Rails - belongs_to ignores inverse_of - ruby-on-rails-3

I've got two AR Models:
class User < ActiveRecord::Base
set_primary_key 'email'
attr_readonly :email, :etag, :full_name, :google_id, :photo_url, :suspended
has_one :sip_user, inverse_of: :user, foreign_key: 'user_email'
end
class SipUser < ActiveRecord::Base
set_primary_key 'user_email'
attr_readonly :user_email, :sip_id, :sip_password,
:int_number, :ext_number, :mobile_number
belongs_to :user, inverse_of: :sip_user, foreign_key: 'email'
end
Querying from User to SipUser works perfectly:
User.find('email#company.com').sip_user
returns the correct matching SIP user.
SipUser.find('email#company.com')
also returns a correct DB entry, but
SipUser.find('email#company.com').user
doesn't. But it should, according to the belongs_to :user, inverse_of: :sip_user.
Any ideas?

The inverse_of has no nothing to do with the SipUser.find('email#company.com').user expression. You specification in SipUser means this: For this sip_user, find the user that has user.email= sip_user.email. This leads to this query SELECT * FROM USERS WHERE email='email#company.com' LIMIT 1;
Your inverse_of would help with this case sip_user.user.sip_user. If you do not have inverse_of, it will use two query, first one finds the user for the sip_user, and a second one finds the sip_user for the sip_user.user. With inverse_of, it will only send the first query and immediately returns sip_user as sip_user.user.sip_user. So it helps with your DB efficiency.

Related

ActiveRocord query with polymorphic associations

I'm trying to get some records from table, but i don't know how to build this query.
I have some models.
class Request < ActiveRecord::Base
has_many :notifications, as: :source
has_many :decisions, dependent: :destroy
end
class Notification < ActiveRecord::Base
belongs_to :source, polymorphic: true
end
class Decision < ActiveRecord::Base
has_many :notifications, as: :source
belongs_to :request
end
So, I need to get all Notifications where source = some_request or source.request = some_request
Isn't it something simple as -
some_request.notifications
# or
some_decision.notifications
and if source is combination of request & decision then
notifications_ids = some_request.notifications.pluck(:id) +
some_decision.notifications.pluck(:id)
Notification.find(notifications_ids)
Your query should be Notification.where(source_id: some_request.id, source_type: 'Request')
Refer Active record association

Ordering records by the number of associated records

Users can submit resources and post comments.
I want to show the users who are most 'active' by selecting the users who have submitted resources and comments and order the results from the user that has submitted the most resources and comments combined to the least.
**Resource**
has_many :users, :through => :kits
has_many :kits
belongs_to :submitter, class_name: "User"
**User**
has_many :resources, :through => :kits
has_many :kits
has_many :submitted_resources, class_name: "Resource", foreign_key: "submitter_id"
**Kits**
belongs_to :resource
belongs_to :user
**Comments**
belongs_to :user
I am new to this kind of sql in Rails. How can I get this record set?
First, you will need to add the comments association to the User model:
has_many :comments
With this, the simplest solution is to do this:
User.all.sort do |a,b|
(a.submitted_resources.count + a.comments.count) <=> (b.submitted_resources.count + b.comments.count)
end
This will get very slow, so if you want to do better you will want to add counter caches. In a migration:
def up
add_column :users, :submitted_resources_count, :integer
add_column :users, :comments_count, :integer
User.reset_column_information
User.find_each do |u|
u.update_attributes! \
:submitted_resources_count => u.submitted_resources.count,
:comments_count => u.comments.count
end
end
def down
add_column :users, :submitted_resources_count
add_column :users, :comments_count
end
Once you run this migration, you can change the original query to:
User.select('*, (submitted_resources_count + comments_count) AS activity_level').order('activity_level DESC')
This will very efficiently return all users in the proper order, and as a bonus each user will have a read-only attribute called activity_level that will give the exact submitted resources + comments count.

What's the rails way to include a field in a join model when listing an association?

So if I have the following relationship
class Item < ActiveRecord::Base
has_many :item_user_relationships
has_many :users, :through => :item_user_relationships
end
class User < ActiveRecord::Base
has_many :item_user_relationships
has_many :items, :through => :item_user_relationships
end
class ItemUserRelationship < ActiveRecord::Base
belongs_to :item
belongs_to :user
attr_accessible :role
end
What's the rails way to include the role attribute when listing all the Users of an Item?
#users = #item.users # I want to include the role as part of a user
Thanks!
UPDATE: I'm still having trouble with this. My goal is to get an array of User models that have their role included in the attributes.
I'm note sure if I understand you correctly, but maybe this is what you want?
#users = #item.users
#users.each do |user|
puts user.item_user_relationships.first.role
end

Avoiding individual database calls for count

My models look like this:
class Movie < ActiveRecord::Base
attr_accessible :title, :year, :rotten_id, :audience_score,
:critics_score, :runtime, :synopsis, :link, :image
has_many :jobs, :dependent => :destroy
has_many :actors, :through => :jobs
end
class Actor < ActiveRecord::Base
attr_accessible :name
has_many :movies, :through => :jobs
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
attr_accessible :movie_id, :actor_id
belongs_to :movie
belongs_to :actor
end
When I'm displaying my index of Actors, I'd like to show the number of movies each actor has starred in. I can do this with #actor.movies.count, however this generates an SQL query for each actor. With, say, 30 actors, this will result in 30 extra queries in addition to the initial.
Is there any way to include the count of movies each actor has participated in, in the initial Actor.all call? And thereby getting things done with only one call. Extra bonus if this was sorted by said count.
Update:
All answers provided has been helpful, and though it turned into some dirt-slinging-contest at some point, it worked out well. I did a mish-mash of all your suggestions. I added a movies_counter column to my Actor model. In my Job model I added belongs_to :actor, :counter_cache => :movies_counter. This works brilliantly, and is automatically updated when i create or destroy a movie, without me adding any further code.
As #Sam noticed, you should add new column to actors table movies_counter
rails g migration add_movies_counter_to_actor movies_counter:integer
Now you can edit your migration
class AddMoviesCounterToActor < ActiveRecord::Migration
def self.up
add_column :actors, :movies_counter, :integer, :default => 0
Actor.reset_column_information
Actor.all.each do |a|
a.update_attribute :movies_counter, a.movies.count
end
end
def self.down
remove_column :actors, :movies_counter
end
end
And run it
rake db:migrate
Then you should add two callbacks: after_save and after_destroy
class Movie < ActiveRecord::Base
attr_accessible :title, :year, :rotten_id, :audience_score,
:critics_score, :runtime, :synopsis, :link, :image
has_many :jobs, :dependent => :destroy
has_many :actors, :through => :jobs
after_save :update_movie_counter
after_destroy :update_movie_counter
private
def update_movie_counter
self.actors.each do |actor|
actor.update_attribute(:movie_count, actor.movies.count)
end
end
end
Then you can call some_actor.movies_counter
Add a column to your Actor table called 'movie_count'. Then add a call back in your Actor model that updates that column.
class Movie < ActiveRecord::Base
has_many :actors, :through => :jobs
before_save :update_movie_count
def update_movie_count
self.actor.update_attribute(:movie_count, self.movies.size)
end
end
That way your just have an integer that gets updated instead of calling all records.

counter_cache has_many_through sql optimisation, reduce number of sql queries

How I can optimise my SQL queries, to ignore situations like this:
Meeting.find(5).users.size => SELECT COUNT(*) FROM ... WHERE ...
User.find(123).meetings.size => SELECT COUNT(*) FROm ... WHERE ...
I have no idea how to use counter_cache here.
Here is my model relation:
class Meeting < ActiveRecord::Base
has_many :meeting_users
has_many :users, :through => meeting_users
end
class User < ActiveRecord::Base
has_many :meeting_users
has_many :meetings, :through => meeting_users
end
class Meeting_user < ActiveRecord::Base
belongs_to :meeting
belongs_to :user
end
What are the most optimal solutions ?
And how implement counter_cache here ?
Starting from Rails3.0.5 and in newer versions, you are now able to set counter cache to the "linker" model, in your case it will be:
class MeetingUser < ActiveRecord::Base
belongs_to :meeting, :counter_cache => :users_count
belongs_to :user, :counter_cache => :meetings_count
end
It's important to explicitly specify count column names, otherwise the columns used will default to meeting_users_count.
As far as I know you can't use counter_cache with through associations, that's why you should manually increment it.
For example (untested):
class MeetingUser < ActiveRecord::Base
...
after_create { |record|
Meeting.increment_counter(:users_count, record.meeting.id)
}
after_destroy { |record|
Meeting.decrement_counter(:users_count, record.meeting.id)
}
end