Elegant Advanced Rails Queries - mysql

I'm having trouble wrapping my head around more advanced Rails query methods. In this case, I have three tables: Users, Friendships, and Events.
I have set it up as follows in the models for each
User-> :has_many => :friendships, :has_many => :items
Friendship-> :belongs_to => :user
Event-> belongs_to => :user
Friendships are simply two columns: "user_id" and "friend_id" so each new friendship would create two new rows.
Here is the query I'm using to find "the first four events belonging to a friend of the current_user that has a start_date that is later than right now."
find_by_sql(["SELECT DISTINCT e.id
FROM events e
WHERE e.start_date > ?
AND e.user_id IN(
SELECT f.user_id
FROM friendships f
WHERE f.friend_id = ?)
LIMIT 0,4", Time.zone.now,current_user.id])
What is the true and elegant way to do something like this in Rails? I have a feeling this is also extremely inefficient...

You should be able to use :join and :conditions
time_range = (Time.now.midnight - 4.days)..Time.now.midnight
Event.all :joins => :friendships, :conditions =>
{'start_date' => time_range, 'friendships.friend_id' => current_user.id}, :limit => 4
I haven't really done a whole lot of complex querying like this, but I pieced this together from the examples here: http://guides.rubyonrails.org/active_record_querying.html
Edit:
You may have to add to Event:
has_many :friends, :through => :friendships
Edit2: Looks like you'll have to actually use a nested join:
Event.all :joins => {:user => :friendships }, :conditions => {'start_date' => time_range, 'friendships.friend_id' => current_user.id }
Here is the model code that I used:
class User < ActiveRecord::Base
has_many :events, :foreign_key => "user_id"
has_many :friendships
has_many :friends, :through => :friendships, :source => :user, :uniq => true
end
class Event < ActiveRecord::Base
belongs_to :user
has_many :friendships, :through => :user
end
class Friendship < ActiveRecord::Base
belongs_to :user
end
And, some example output if that may be helpful at all:
>> Event.all :joins => {:user => :friendships }, :conditions => {'happens_on' => time_range, 'friendships.user_id' => 1 }
Event.all :joins => {:user => :friendships }, :conditions => {'happens_on' => time_range, 'friendships.user_id' => 1 }
=> [#<Event id: 1, happens_on: "2010-08-06 00:42:37", title: "Jims party", description: "Happy Birthday", created_at: "2010-08-03 00:42:37", updated_at: "2010-08-03 00:42:37", user_id: 1>]
>> jim = User.find(1)
jim = User.find(1)
=> #<User id: 1, name: "Jim", bio: "Loves Rails", age: 30, created_at: "2010-08-03 00:30:51", updated_at: "2010-08-03 00:30:51">
>> crystal = User.find(2)
crystal = User.find(2)
=> #<User id: 2, name: "Crystal", bio: "Loves writing", age: 26, created_at: "2010-08-03 00:31:14", updated_at: "2010-08-03 00:31:14">
>> jim.events
jim.events
=> [#<Event id: 1, happens_on: "2010-08-06 00:42:37", title: "Jims party", description: "Happy Birthday", created_at: "2010-08-03 00:42:37", updated_at: "2010-08-03 00:42:37", user_id: 1>]
>> event1 = jim.events[0]
event1 = jim.events[0]
=> #<Event id: 1, happens_on: "2010-08-06 00:42:37", title: "Jims party", description: "Happy Birthday", created_at: "2010-08-03 00:42:37", updated_at: "2010-08-03 00:42:37", user_id: 1>
>> event1.user
event1.user
=> #<User id: 1, name: "Jim", bio: "Loves Rails", age: 30, created_at: "2010-08-03 00:30:51", updated_at: "2010-08-03 00:30:51">
>> event1.friendships
event1.friendships
=> [#<Friendship user_id: 1, friend_id: nil, created_at: "2010-08-03 00:57:31", updated_at: "2010-08-03 00:57:31">]
>>

Related

How to query rails way ? Rails 3.2

List of relationship between models:
class ErrorScope < ActiveRecord::Base
belongs_to :server
has_many :scope_to_fixflow_map
attr_accessible :id, :server_id, :error_codes, :scoping_method, :priority, :error_codes_is_wildcard_match
serialize :error_codes
.....
end
class ScopeToFixflowMap < ActiveRecord::Base
belongs_to :error_scope
attr_accessible :id, :server_id, :error_scope_id, :path, :fixflow_class_name
......
end
class Server < ActiveRecord::Base
has_many :error_scopes
......
end
schema.rb
create_table "error_scopes", :force => true do |t|
t.integer "server_id", :limit => 8, :null => false
t.text "error_codes", :null => false
t.text "scoping_method"
t.integer "priority", :null => false
t.boolean "error_codes_is_wildcard_match", :default => false
end
create_table "scope_to_fixflow_maps", :force => true do |t|
t.integer "server_id", :limit => 8, :null => false
t.integer "error_scope_id", :limit => 8, :null => false
t.string "path"
t.string "fixflow_class_name", :null => false
end
Now i have a sql query which gives me desired output:
SELECT fixflow_class_name
FROM error_scopes s
join scope_to_fixflow_maps m on s.id=m.error_scope_id
join servers serv on serv.id=s.server_id
where error_codes regexp 'error_scope_test'
and path = 'x'
and assettag = 'y'
What I tried so far. It works
ErrorScope.where("error_codes like ?", "%error_scope_test\n%").select {|tag| tag.server.assettag == "y"}[0].scope_to_fixflow_map.select {|y| y.path == "x"}[0].fixflow_class_name
using joins
ErrorScope.joins(:server, :scope_to_fixflow_map).where("error_codes LIKE ?", "%error_scope_test%").select {|tag| tag.server.assettag == "y"}[0].scope_to_fixflow_map.select {|y| y.path == "x"}[0].fixflow_class_name
I am sure there must be better way to do this query??
Something like this:
ErrorScope.joins(:server, :scope_to_fixflow_map)
.where("error_codes LIKE ?", "%error_scope_test%")
.where("servers.assettag='y'")
.where("scope_to_fixflow_maps.path='x'")
.select("scope_to_fixflow_maps.fixflow_class_name")
Not the rails way but quick and dirty:
ActiveRecord::Base.execute("SELECT fixflow_class_name
FROM error_scopes s
join scope_to_fixflow_maps m on s.id=m.error_scope_id
join servers serv on serv.id=s.server_id
where error_codes regexp 'error_scope_test'
and path = 'x'
and assettag = 'y'")
Returns back an array of hashes that you can work with

Rails has_many/belongs_to on custom database ActiveRecord::AssociationTypeMismatch got Fixnum

I have the database from another non-rails project so I have to deal with unordinary column names. I have the model Category:
self.primary_key = "categoryID"
has_many :products, foreign_key: "category", primary_key: "categoryID"
And the model Product:
self.primary_key = "productID"
belongs_to :category, foreign_key: "category", primary_key: "categoryID"
In the Product's table there's a foreign key category which stores a primary key of Category's table, which is categoryID. I'm trying to create a product in a console like that:
c = Category.last
p = c.products.create
And I get an error:
ActiveRecord::AssociationTypeMismatch: Category(#29703600) expected, got Fixnum(#17843240)
I tried some other ways to create a product where I could pass the Category instance there but it leads to other weird errors. So now I just want this way to work.
Where is a problem?
I think the problem is that you have category column (so Rails creates category method for it) and category association with the same name.
You can give some another name to association
I created test app
class CreateProducts < ActiveRecord::Migration
def change
create_table :products, id: false do |t|
t.integer :productID
t.integer :category
t.string :title
end
end
end
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories, id: false do |t|
t.integer :categoryID
t.string :title
end
end
end
class Product < ActiveRecord::Base
self.primary_key = :productID
belongs_to :my_category, class_name: 'Category', foreign_key: :category
end
class Category < ActiveRecord::Base
self.primary_key = :categoryID
has_many :products, foreign_key: :category
end
This way the following code seems to work fine
c = Category.create categoryID: 1, title: 'First category'
c.products # => []
c.products.create productID: 1, title: 'First product'
c.products.create productID: 2, title: 'Second product'
c.products # => #<ActiveRecord::Associations::CollectionProxy [#<Product productID: 1, category: 1, title: "First product">, #<Product productID: 2, category: 1, title: "Second product">]>
p = Product.first
p.category # => 1
p.my_category # => #<Category categoryID: 1, title: "First category">

Why can I not update the foreign key of an object in a 1:1 association?

I have two models, User and Profile.
A User has_one Profile and a Profile belongs_to User.
Correspondingly, the Profile model has a user_id attribute.
The association works:
p = Profile.first
=> #<Profile id: 1, name: "Jack", ... , user_id: 1>
u = User.first
=> #<User id: 1, email: "jack#example.com", ... >
u.profile.id
=> 1
p.user.id
=> 1
p.user == u
=> true
u.profile == p
=> true
I can set the user_id field on a Profile directly:
p.user_id = 2
=> 2
p.save!
=> true
p.user_id
=> 2
But why can I not set the user_id like this:
u.profile.user_id = 2
=> 2
u.profile.save!
=> 2
u.profile.user_id
=> 1
You must refresh u.profile object. Try this:
u.profile.user_id = 2
=> 2
u.profile.save!
=> 2
u.profile.reload.user_id
=> 2
This is because original profile object is still loaded on memory in u.
Hope this help :)

Recursive many to many relationship on Rails

For my project, I have some Post linked to Category. What I want to achieve, is to have Category who are relatives to others. Like this :
c1 = Category.create(name: 'Television')
c2 = Category.create(name: 'TV')
c1.relatives << c1
I use a join table :
create_table :category_relative, id: false do |t|
t.belongs_to :category_1
t.belongs_to :category_2
end
add_index :category_relative, [:category_1_id, :category_2_id]
So far, I have tried this :
class Category < ActiveRecord::Base
has_and_belongs_to_many :relatives, class_name: 'Category',
join_table: 'category_relative', foreign_key: 'category_1_id',
association_foreign_key: 'category_2_id'
end
This is working, but only in a single way :
c1.relatives
=> []
c2.relatives
=> [#<Category:0x007fcc610b8418 id: 1, name: 'Television']
I know that I can add a relative for each entry, but this is too heavy for my database :
c1.relatives << c2
c2.relatives << c1
Do you have any idea ? Should I write the JOIN manually ?
You can try something like this
class Category < ActiveRecord::Base
has_many :category_relatives, dependent: :destroy
has_many :relatives, :through => :category_relative
has_many :inverse_category_relatives, :class_name => "categoryRelative", :foreign_key => "category_2_id", dependent: :destroy
has_many :inverse_relatives, :through => :inverse_category_relative, :source => :category
end
Through class:
class CategoryRelative < ActiveRecord::Base
belongs_to :category
belongs_to :relative, :class_name => "Category"
end
With this code you have a many to many relationship between the same class thourgh another

How to Resolve MySQL Error? Chaining Named Scopes

I'm trying to chain two named_scopes in my User model.
The first:
named_scope :commentors, lambda { |*args|
{ :select => 'users.*, count(*) as total_comments',
:joins => :comments,
:conditions => { :comments => { :public_comment => 1, :aasm_state => 'posted', :talkboard_user_id => nil} },
:group => 'users.id',
:having => ['total_comments > ?', args.first || 0],
:order => 'total_comments desc' }
}
The second:
named_scope :not_awarded_badge, lambda { |badge_id|
{ :include => :awards,
:conditions => [ "? not in (select awards.badge_id from awards where awards.user_id = users.id)", badge_id ]
}
}
I am trying to chain the two like this:
User.commentors(25).not_awarded_badge(1)
However, I get the following error:
Mysql::Error: Unknown column 'total_comments' in 'order clause': SELECT `users`.`id`...
How can I resolve this issue?
change
:order => 'total_comments desc'
to
:order => 'count(*) desc'
it should like
named_scope :commentors, lambda { |*args|
{ :select => 'users.*, count(*) as total_comments',
:joins => :comments,
:conditions => { :comments => { :public_comment => 1, :aasm_state => 'posted', :talkboard_user_id => nil} },
:group => 'users.id',
:having => ['total_comments > ?', args.first || 0],
:order => 'count(*) desc' }
}