rails relationships, 3 levels at least - ruby-on-rails

How can i authentically use rails relationships in my case?
I'm making a nightlife website, just for you to know the context. It has three models, i want to join on one page: Places, Albums, Photos.
So, photos in albums work great, i use there a custom-coded sql with joins.
But when i click on a place i want to see info from place model and a list of albums with a sample photo (first for example)
Now i have the following:
class Place < ActiveRecord::Base
has_many :albums
end
class Album < ActiveRecord::Base
has_many :photos
has_one :place
end
class Photo < ActiveRecord::Base
belongs_to :album
end
And there how i get it in my controller:
#place = Place.find( params[:id], :include => :albums )
and i get #place.albums as an object with albums data. But i dont know how to dig even deeper to reach thephotosmodel. And anywhay i can:include => :albumsbecause i have no direct relations betweenplaceandalbum, but i have them inalbum > photo` so sql join is needed?
That's a complicated question, but i just don`t get it fully.
Thank you for any help, maybe a good link to read!

First of all, the association between Place and Album isn't correct. The way you have it setup, it looks like neither would have a foreign_key. In other words, it should be Album belongs_to :place, with place_id in the albums table.
Second of all, to get the first photo for each album:
<% #place.albums.each do |album| %>
Sample Photo: <%= image_tag(album.photos.first.url) %>
<% end %>
If you really wanted to, you could get the first photo from the first album of a place in one line:
<%= image_tag(#place.albums.first.photos.first.url) %>
First note that this is prone to nil errors if you don't add in conditionals, so I don't actually recommend this method. But, if you were to use it, I would just add a method to the Place model:
def first_photo
albums.first.photos.first
end
And in your view:
<%= image_tag(#place.first_photo.url) %>
You'll want to join this stuff together as well so you're not performing too many queries. Wizard of Ogz answer covers that.

There is a hash syntax for nested includes. It also works for joins.
#place = Place.find( params[:id], :include => {:albums => :photos} )
Here is the documentation for this http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Look under the heading "Eager loading of associations"

Related

Accessing the associated join model when iterating through a has_many :through association

I have a feeling this is a pretty basic question, but for some reason I'm stumped by it (Rails newbie) and can't seem to find the answer (which may be I'm not searching properly).
So I have a basic has_many :through relationship like this:
class User < ApplicationRecord
has_many :contacts, through :user_contacts
class Contact < ApplicationRecord
has_many :users, through :user_contacts
In users/show.html.erb I'm iterating through a single user's contacts, like:
<% #user.contacts.each do |c| %>
<%= c.name %>
<% end %>
Now inside of that each loop, I want to access the user_contact join model that's associated with the given user and contact in order to display the created_at timestamp that indicates when the user <--> contact relationship was made.
I know I could just do a UserContact.find call to look up the model in the database by the user_id and contact_id but somehow this feels superfluous. If I understand correctly how this works (it's entirely possible I don't) the user_contact model should have already been loaded when I loaded the given user and its contacts from the database already. I just don't know how to properly access the correct model. Can someone help with the correct syntax?
Actually the join model will not have been loaded yet: ActiveRecord takes the through specification to build its SQL JOIN statements for querying the correct Contact records but effectively will only instantiate those.
Assuming you have a UserContact model, you could do sth like this:
#user.user_contacts.includes(:contact).find_each do |uc|
# now you can access both join model and contact without additional queries to the DB
end
If you want to keep things readable without cluttering your code with uc.contact.something, you can set up delegations inside the UserContact model that delegate some properties to contact or user respectively. For example this
class UserContact < ActiveRecord::Base
belongs_to :user
belongs_to :contact
delegate :name, to: :contact, prefix: true
end
would allow you to write
uc.contact_name
First of all, the has_many :things, through: :other_things clause is going to look for the other_things relationship to find :things.
Think of it as a method call of sorts with magic built in to make it performant in SQL queries. So by using a through clause you're more or less doing something like:
def contacts
user_contacts.map { |user_contact| user_contact.contacts }.flatten
end
The context of the user_contacts is completely lost.
Since it looks like user_contacts is a one-to-one join. It would be easier to do something like this:
<% #user.user_contacts.each do |user_contact| %>
<%= user_contact.contact.name %>
<% end %>
Also since you're new to Rails it's worth mentioning that to load those records without an N+1 query you can do something like this in your controller:
#user = User.includes(user_contacts: [:contacts]).find(params[:id])
Use .joins and .select in this way:
#contacts = current_user.contacts.joins(user_contacts: :users).select('contacts.*, user_contacts.user_contact_attribute_name as user_contact_attribute_name')
Now, inside #contacts.each do |contact| loop, you can call contact.user_contact_attribute_name.
It looks weird because contact doesn't have that user_contact_attribute_name, only UserContact does, but the .select portion of the query will make that magically available to you on each contact instance.
The contacts.* portion is what tells the query to make all contact's attributes available as well.

Choosing a specific row in a table in rails

I have a migration and model with a table called medications. I need to pick a specific row from the medications table. I also am trying to filter out all medications that don't have the current user's id.
Here is the current code I have.
Medication.find(:name, :conditions => { :user_id => current_user.id }, :order => "Medication.name")
I know this isn't complete, but any help would be greatly appreciated.
You can load the first medication for a specific user_id like this (assuming that your medications table has an user_id):
Medication.where(user_id: current_user.id).order(:name).first
When our User model has a belongs_to :medications it can be simplified to:
current_user.medications.order(:name).first
When you want to load the e.g. 5th medication just add an offset of 4:
current_user.medications.order(:name).offest(4).first
Or load all medications and iterate through them:
current_user.medications.limit(10).each do |medication|
puts medication.name
end
When you want to output the first ten medications on a website you would do something like this:
# in the controller
#medications = current_user.medications.order(:name).limit(10)
# in the view
<ul>
<% #medications.each do |medication| %>
<li><%= medication.name %></li>
< end %>
</ul>
The finder syntax you use is deprecated and was replaced in Rails 4. See Rails Guide about querying the database.
This is a perfect use case for a has_many :through association if you don't already have it set up.
class User < ActiveRecord::Base
has_many :prescriptions # or whatever
has_many :medications, :through => :prescriptions
end
class Prescription < ActiveRecord::Base
belongs_to :user
belongs_to :medication
end
class Medication < ActiveRecord::Base
has_many :prescriptions
has_many :users, :through => :prescriptions
end
Now you can do stuff like #user.medications to retrieve only that user's medications, #user.medications.find(params[:medication_id] to find a specific one within a user's assigned medications, and #user.medications << Medication.find_by(name: 'Aspirin') to add a medication to a user, and so on.
This is a basic overview of this technique, but it's a basic Rails concept so there's plenty of information on use cases close to whatever you may be trying to do.
I fixed the problem and I have decided to post the answer in case anybody else seems to have a similar problem.
I ended up not putting anything in my controller or adding anything new to my models. I just used this line of code in the view.
<%= Medication.offset(0).where(:user_id => current_user.id).pluck(:name).first %>
I couldn't have done it without the support of everyone who posted, Thank you!

Ruby on Rails 4 Nested Forms with Active Record

I am new to Rails and just building my first app (coming from a PHP and .NET background and loving it btw) but I have run into a problem that I am struggling to find an answer to, even though I am sure there is an easy one!!
My project has 3 main Models; Locations, Services and Location Services
There are multiple services available and a Location can have any number of them. Basically I am using a record in Locations Services to store the ID of the selected service and the ID of the location.
A simplified version of my models are as below:
class Location < ActiveRecord::Base
has_many :location_services
end
class Service < ActiveRecord::Base
has_many :location_services
end
class LocationService < ActiveRecord::Base
belongs_to :location
belongs_to :service
end
I have read up about nested forms and using 'accepts_nested_attributes_for' to allow sub forms to edit data taken from another model which sounds very similar to what I want, except I don't want to just be able to edit the Location Services that I have, I want to be able to choose from every single available Service as checkboxes, then when checked and my Location is saved, I want it to create a record for each selected service in the Location Services table using the ID of the Location and the ID of the service
Im sure I could easily generate all the tickboxes with Services.all and then loop through that and then in my controller grab all of the ticked checkboxes from the POST, loop through them and build an array of all of them and then pass that array to Location.location_services.create([]) but this is rails and I feel like there is probably a better way to do it?
So firstly, am i going about this in a stupid way? Rather than having 3 tables, is there a better way of doing it? And is there a nice way of generating and saving all of the services?
Many thanks in advance
David
Many thanks Yan for your help on this one, I have finally managed to resolve my issue and it actually turned out to be really simple. I am posting here in the hope it helps someone else.
What I needed to do was add a has_many relation to services through location services so my model now looks like below:
class Location < ActiveRecord::Base
has_many :services, :through => :location_services
has_many :location_services
accepts_nested_attributes_for :location_services
end
I updated my view to include:
<%= f.collection_check_boxes(:service_ids, Service.all, :id, :name) do |b| %>
<%= b.label(class: "check_box") do %>
<%= b.check_box %>
<%= b.object.name %>
<% end %>
<% end %>
Then in my controller I have:
def location_params
params.require(:location).permit(:service_ids => [])
end
I have stripped out all of my other fields for simplicity. Then finally in the Update method, it is as simple as:
def update
#location.update(location_params)
redirect_to #location, notice: 'Location was successfully updated.'
end
Hope this helps someone out!!
Many thanks
David
A has_many relationship adds a number of methods to your model. From which you only need the collection_singular_ids method, which does the following:
Replace the collection with the objects identified by the primary keys
in ids. This method loads the models and calls collection=.
The above method can be combined with collection_check_boxes as explained in this tutorial. So in your case you'll have something like:
f.collection_check_boxes :location_service_ids, LocationService.all, :id, :name
Note that the last parameter (:name here) is the text_method_option which generates the labels for your check boxes.
Last but not least: don't forget to use accepts_nested_attributes properly.

How to eager loading when there are further conditions on association objects?

I am using Ruby on Rails 3.2.2 and I have the following has_many :through association in order to "order articles in categories":
class Article < ActiveRecord::Base
has_many :category_associations # Association objects
has_many :associated_categories, :through => :category_associations # Associated objects
end
class CategoryAssociation < ActiveRecord::Base
acts_as_list :scope => 'category_id = #{category_id} AND creator_user_id = #{creator_user_id}'
belongs_to :associated_article
belongs_to :creator_user, :foreign_key => 'creator_user_id'
end
On retrieving associated_categories I would like to load category_associations objects created by a user (note: the creator user is identified by the creator_user_id column present in the category_associations database table) because I need to display position values (note: the position attribute, an Integer, is required by the act_as_list gem and it is a column present in the category_associations database table) "near" each article title.
Practically speaking, in my view I would like to make something like the following in a proper and performant way (note: It is assumed that each article in #articles is "category-associated" by a user - the user refers to the mentioned creator user of category_associations):
<% #articles.each do |article| %>
<%= link_to(article.title, article_path(article)) %> (<%= # Display the article position in the given category %>)
<% end %>
Probably, I should "create" and "handle" a custom data structure (or, maybe, I should make some else...), but I do not how to proceed to accomplish what I am looking for.
At this time I am thinking that the eager loading is a good approach for my case because I could avoid the N + 1 queries problem since I have to state further conditions on association objects in order to:
retrieve specific attribute values (in my case those refer to position values) of association objects created by a given user;
"relate" (in some way, so that position values are suitable for displaing) each of those specific attribute values to the corresponding associated object.
I think, you are looking for this
#articles = Article.includes(:associated_categories)
This will eager load all your articles including both of its associations (associated_categories, associated_categories). Thus, it will avoid N+1 problem and wont fire queries when you iterate over #articles and its associations in your view.

Ruby on Rails - Creating a form for a collection of objects that have no parent

Under more typical circumstances (ie the objects being edited/created belong to another model) the following would work:
models
Person < AR:Base
has_many :things
end
Thing < AR:Base
belongs_to :person
end
haml
=form_for #person do |f|
=f.fields_for :things do |thing_form|
=thing_form.description
However I need to edit a collection of things (queried from the database .. select * from things where created_at > 2012-01-01) without consideration for the Person they belong to (some don't even belong to a person).
My fields_for /should/ look something like this, but I'm not sure how to set it up before this (as I have no object to build the form from)
...
-#things.each do |thing|
=f.fields_for :thing, thing do |thing_fields|
=thing_form.description
if I could make a 'dummy' person and load the things array with my selection of things, then save it Person without actually saving the dummy, that would work... but how? :)
I have same problem for categories of user. See my solution below.
- Category.all.each do |c|
= check_box_tag "user[category_ids][]", c.id, #user.categories.include?(c), :id => "user_category_ids_#{c.id}"

Resources