Rails scope with nested association - ruby

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)

Related

Activerecord/Datamapper - Have one child belong to many parents

How would you set up an activerecord/datamapper association for the following scenario:
A user creates a "bookshelf" which has many books(a book object just has an isbn that is used to query an api, and has_many review objects associated with it). Let's say Jack creates a "bookshelf" with a book object. Then, lets say that Jill creates a "bookshelf" with the same book object(it has the same id and the same reviews). The book object has the following code as of now:
class Book < ActiveRecord::Base
has_many :reviews
end
Then, when you view the page for a book (you click the link to it from the "bookshelf" created by Jack) you should see the same book object when you clicked the link to it from Jill's "bookshelf" (e.g. both "bookshelves" have a link to /books/23 because they have the same book object).
I have not been able to figure this out with the has_many association because that requires me to make a new book each time a user adds a book to their "bookshelf." I have trouble understanding the has_and_belongs_to_many relationship, is that what should be used here? I was not able to find any similar questions on SO, so any help is greatly appreciated.
I am using Rails 4 with Ruby 2.1.
Here is a drawing of what I would like to accomplish:
Drawing
Yes, you would have to define many-to-many relationship between a Bookshelf and a Book. There are two ways to achieve this in Rails:
Option 1) Use has_and_belongs_to_many
See guide
According to official documentation has_and_belongs_to_many association:
Specifies a many-to-many relationship with another class. This associates two classes via an intermediate join table. Unless the join table is explicitly specified as an option, it is guessed using the lexical order of the class names. So a join between Developer and Project will give the default join table name of “developers_projects” because “D” precedes “P” alphabetically.
So, your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_and_belongs_to_many :books
end
class Book < ActiveRecord::Base
has_and_belongs_to_many :bookshelves
has_many :reviews
end
Add a join table generation to your migrations:
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
end
end
end
This will create a books_bookshelves table in your database. The table will have no primary key. There would be two foreign keys to your models Book and Bookshelf.
So, if you call self.books in the context of an user's bookshelf, you will get a list of books in the bookshelf. Vice versa, calling self.bookshelves in the context of a book will return a set of bookshelves the book belongs to.
The problem with this approach is that every time you add a new book to the bookshelf a new record is created in the database. If you are okay with that, there is no easier option than using has_and_belongs_to_many association. Otherwise, I recommend you to go with the Option #2.
Option 2) Use has_many :through
Another option is to use has_many, :through association (see guide). You would have to define one more model to do that, but it might come handy in some use cases (see below for an example).
Your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_many :books, through: :books_bookshelves
has_many :books_bookshelves
end
class Book < ActiveRecord::Base
has_many :bookshelves, through: :books_bookshelves
has_many :books_bookshelves
has_many :reviews
end
class BooksBookshelf < ActiveRecord::Base
belongs_to :book
belongs_to :bookshelf
end
Probably the best thing about using has_many :through association is that it allows you to add custom columns to the join table (e.g. add column count to keep track how many books of the same type are there in the bookshelf).
The migration would look pretty much the same as the one we used in Option 1, except for the fact we are adding an unique constraint on the foreign keys (please note that adding the constraint is optional):
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
# add your custom columns here
end
add_index :books_bookshelves, [:book_id, :bookshelf_id], unique: true # to make sure you won't create duplicate records
end
end
By going with this approach, adding a new would be a bit more complicated as you would have to make sure you are not inserting duplicate records in the join table. (However, you may remove the unique constraint from the migration, to achieve exactly the same kind of behavior as you would get with has_and_belongs_to_many.)

Rails association, I want something but the other model shouldn't care

So I think I need help with what I am doing with my asscoiations. I have two models (well many but for the purposes here). One is a Provider. Each provider can have a designation. Though the Designations is just a list it can appear in many different providers so it is not one to one. My models are as follows:
Provider.rb
class Provider < ActiveRecord::Base
has_and_belongs_to_many :groups
has_one :designation
has_one :specialty
end
Designation.rb
class Designation < ActiveRecord::Base
end
The error I get is this:
SQLite3::SQLException: no such column: designations.provider_id: SELECT "designations".* FROM "designations" WHERE "designations"."provider_id" = ? LIMIT 1
Which tells me my associations are off cause the designations should not have the provider_id, if anything the provider should have the designation_id.
Do I ditch the has_one and just put has_many in the Designation?
If I do that, I am new to rails, do I have to create a migration so the database is correctly updated?
Try using
models/provider.rb
belongs_to :designation
Then in
models/designation.rb
has_many :providers
It may feel a little strange but the belongs_to just lets you know which model the id column needs to go in. In this case the provider model, so you'll need:
rails g migration add_designation_id_to_provider designation_id:integer

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.

Join type in ActiveRecord has_one Relationship

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)

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