Join type in ActiveRecord has_one Relationship - ruby

Just getting started with ActiveRecord (in a sinatra app). Trying to port existing queries to AR but getting a little stuck.
if i have a has_one relation for users and profiles (using legacy tables unfortunately)
class User < ActiveRecord::Base
self.table_name = "systemUsers"
self.primary_key = "user_id"
has_one :profile, class_name: 'Profile', foreign_key: 'profile_user_id'
end
class Profile < ActiveRecord::Base
self.table_name = "systemUserProfiles"
self.primary_key = "profile_id"
belongs_to :user, class_name: "User", foreign_key: 'user_id'
end
if i want to query all users with profile using an inner join then get the user_age field from the profiles using one query can i do it?
for example
(just added .first to reduce code but would be looping through all users with profiles)
user = User.all(:joins => :profile).first
user.profile.user_age
gives me the correct data and uses a INNER join for the first query but then issues a second query to get the profile data
it also gives a depreciated warning and suggests i use load, which i tried but won't use an inner join.
similar case with
user = User.joins(:profile).first
user.profile.user_age
i get an INNER join but a query for each user row.
I have tried includes
user = User.includes(:profile).first
user.profile.user_age
This lazy loads the profile and would reduce the number of queries in the loop, however i think it would pull users without a profile too
I also tried with a reference
user = User.includes(:profile).references(:profile).first
user.profile.user_age
This gives me the correct data and reduces the queries to 1 but uses a LEFT JOIN
I probably have not quite grasped it and am trying to achieve something thats not do-able, i figured i might either need to use includes and check for nil profiles inside the loop or use joins and accept the additional query for each row.
Thought i'd check incase i was missing something obvious.
Cheers
Pat.

Profile should always have one user. So, I would do Profile.first.user_age for the first user profile. But going by the user approach like you did,
User.find { |u| u.profile }.profile.user_age
User.find { |u| u.profile } returns the first user with true value.
To query all the user profiles and get their user_ages. Assuming all profiles has user_id and that should be the case.
Profile.pluck(:user_age)
This checks the presence of user_id if you save profiles without user id. This where.not is a new feature in Activerecord, check your version.
Profile.where.not(user_id: nil).pluck(:user_age)

Related

Rails scope with nested association

I'm trying to get to grips with rails scopes. I have the simple basics down but I'm trying to create a slightly more complex scope and I'm having some trouble.
class Client
has_many :referrals, through: :submissions
has_one :address
has_many :submissions
end
class Submission
belongs_to :client
belongs_to :user
has_many :referrals, :inverse_of => :submission, dependent: :delete_all
end
class Referral
belongs_to :branch
belongs_to :submission
has_one :client, through: :submission
end
class Address
belongs_to :client
end
I also have users created using devise. I have a custom attribute added to users called city_town.
When a user signs up, they select what city or town they are from and the agency that they work for. When submissions are created, they take nested attributes for client details and address, as well as referral details. referrals take an agency_id that specifies where that referral is going to.
What I'm trying to achieve is to create a scope that will collect all referrals where the referral.client.address.city_town matches the current user's city or town i.e: current_user.city_town and the agency_id of the referral matched the agency_id of the signed in user.
In short, when a user signs in, they can see referrals only for their agency and area.
So far I've got:
class Referral
scope :agency_referrals, -> (id, city_town) { includes(:clients).where(agency_id: id, client.address.city_town => city_town) }
end
but I'm painfully aware that this is far from correct. I get this error:
undefined local variable or method `client' for #<Class:0x00000003200c08>
Any idea where I'm going wrong?
You are receiving this particular error because you are improperly referencing the city_town in your query. client.address.city_town => city_town would imply that the key in this hash is a value nested within an existing "client" variable. This is more correct, and will likely remove the particular error you've just encountered:
By the way, the commenter Pavan was correct, :client should be singular in your .includes() statement. That said, I also expanded it to include the Address table up front, which may have caused additional errors.
# The .includes() parameters have been changed, and quotes have been added to the query.
scope :agency_referrals, -> (id, city_town) { includes(client: :address).where(agency_id: id, 'client.address.city_town' => city_town) }
Also, if you're looking for a non-string method of referencing the proper location (because this is Ruby, and we love our symbols), you could write this:
includes(:clients).where(agency_id: id, client: { address: { city_town: city_town } } => city_town)
I personally find this to be less clean, but it is a more "modern" format.
For further reference, you may wish to review this documentation.
could you try
includes(submission: :clients)

Access attribute in Active Record 'through' association object

I have two classes, User and Product in a 'many-to-many through' association, using the class Prole (for product role).
class User < ActiveRecord::Base
has_many :proles
has_many :products, through: :proles
end
class Product < ActiveRecord::Base
has_many :proles
has_many :users, through: :proles
end
class Prole < ActiveRecord::Base
# has an attribute called 'role'
end
prole has an attribute called role which I'd like to use to qualify the user-product association.
The association works fine, but I can't figure out how to access the role attribute after creating the association. For example, if I do:
user.products << product
how can I access the attribute in the prole object just created?
I guess I could iterate through the prole objects and find the correct one, but I'm hoping there's a cleaner way.
Is this possible? Any hints?
TIA.
I was hoping for something a little more direct, but here's a
POSSIBLE ANSWER:
prole = Prole.find_by user_id: user.id, product_id: product.id
or even better
prole = user.proles.where("product_id = #{product.id}")
After some testing, it looks like the easiest way to grab specifically the Prole object that was just created is by querying by the two foreign keys directly against the Prole model, as suggested in your possible answer.
Prole.find_by(user_id: user.id, product_id: product.id)
If you want it as an association on the user object, you could use the includes approach to do eager loading, but it will still load every prole for the user in question
# specifying proles: {product_id: product.id} in the where clause here
# only limits users retrieved, not proles
user = User.includes(:proles).where(id: user.id)
# eager-loaded prole array
user.proles.find { |prole| prole.product_id == product.id }
See this answer for more info on that. But it looks like your possible answer is the cleanest way.

Rails join on 2 tables

I have 3 tables which are
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Now I want to retrieve all the comments of a particular user.
I have tried it using find_by_sql like
Comment.find_by_sql "SELECT c.* FROM comments c, posts p, users u WHERE c.post_id = p.id and p.user_id = u.id and u.id=6"
This works fine. But I want few details from "posts" table along with comments.
Does anyone have idea how to do that?
Corrected answer..
Now that I've properly understood the scenario, a portion of my previous answer still makes sense with one addition:
Alter the User model like so:
class User < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
end
This type of relationship will perform a join between comments a posts, where the user_id of posts is the current user. Quite literally, like you said in the comments, "the connection between User and Comment [is] through Post".
Similar to what I said before, the center of the universe in this scenario is the user object. With this addition, to grab all the user's comments would simply be:
user.comments
If you look at the log, the SQL output is:
SELECT `comments`.* FROM `comments` INNER JOIN `posts` ON `comments`.`post_id` = `posts`.`id` WHERE `posts`.`user_id` = 1
Which is similar to your original query.
Now that you retrieved the comments for this user, you can simply access the Post model data through the belongs_to :post relationship, as normal.
In the end, unless you have some special reason for trying to do everything in pure SQL, this approach is more readable, maintainable, and the "Rails way". Plus it saves yourself a lot of hassle and typing.
More information on has_many ... through.

Is there a way in MongoMapper to achieve similar behavior as AR's includes method?

Is there a feature equivalent in MongoMapper to this:
class Model < ActiveRecord::Base
belongs_to :x
scope :with_x, includes(:x)
end
When running Model.with_x, this avoids N queries to X.
Is there a similar feature in MongoMapper?
When it's a belongs_to relationship, you can turn on the identity map and run two queries, once for your main documents and then one for all the associated documents. That's the best you can do since Mongo doesn't support joins.
class Comment
include MongoMapper::Document
belongs_to :user
end
class User
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
end
#comments = my_post.comments # query 1
users = User.find(#comments.map(&:user_id)) # query 2
#comments.each do |comment|
comment.user.name # user pulled from identity map, no query fired
end
(Mongoid has a syntax for eager loading, but it works basically the same way.)

Attributes passed to .build() dont show up in the query

Hope your all enjoying your hollydays.
Ive run into a pretty funny problem when trying to insert rows into a really really simple database table.
The basic idea is pretty simple. The user selects one/multiple users in a multiselect which are supposed to be added to a group.
This piece of code will insert a row into the user_group_relationships table, but the users id always
#group = Group.find(params[:group_id])
params[:newMember][:users].each do |uid|
# For debugging purposes.
puts 'Uid:'+uid
#rel = #group.user_group_relationships.build( :user_id => uid.to_i )
#rel.save
end
The user id always gets inserted as null even though it is clearly there. You can see the uid in this example is 5, so it should work.
Uid:5
...
SQL (0.3ms) INSERT INTO
"user_group_relationships"
("created_at", "group_id",
"updated_at", "user_id") VALUES
('2010-12-27 14:03:24.331303', 2,
'2010-12-27 14:03:24.331303', NULL)
Any ideas why this fails?
Looks like user_id is not attr_accessible in the UserGroupRelationship model.
You might also want to check this it may be relevant.
I think #zabba's answer is probably the one you need to look for but i would suggest a couple of extra things here.
Your "Group" and "User" models are connected to each other thru the "UserGroup" model it seems. You would have relationship
class Group < ActiveRecord::Base
has_many :user_group_relationships
has_many :users, :through => :user_group_relationships
end
class UserGroupRelationship < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
In your controller
# Find the group
#group = Group.find(params[:group_id])
# For each user id, find the user and add the user_group_relationship
params[:newMember][:users].each{|u| #group.users << User.find(u) }
Read up Rails Documentation on associations and the methods generated automatically for you when associations are defined. More often than not, working with association is easier than you might think! I discover new APIs in Rails constantly! :) Good Luck!

Resources