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.
Related
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.
I think this is a lot simpler than the title probably lets on. Here are my three models with the associations:
Update: associations were incorrect previously. Here are the corrected associations:
#app/models/call_service.category.rb
class CallServiceCategory < ActiveRecord::Base
has_many :call_services
end
#app/models/call_service.rb
class CallService < ActiveRecord::Base
belongs_to :call_service_category
has_many :calls
end
#app/models/call.rb
class Call < ActiveRecord::Base
belongs_to :call_service
end
So I have a group of call_ids for the calls I want:
#call_ids = [1,2,3,4]
Initial step which works:
What I want to do is grab only the calls with the ids specified in #call_ids. Then, I want to eager load only the associated call_services for those grabbed calls. The following does this perfectly:
#call_ids = [1,2,3,4]
#calls_by_service = CallService.includes(:calls).where("calls.id IN (?)", #call_ids).references(:calls)
This is great. Now I can iterate through only those selected calls' call_services, and I can even list all of those selected calls per service like so:
<% #calls_by_service.each do |call_service| %>
<p> <%= call_service.description %> </p>
<% call_service.calls.each do |call| %>
<%= call.name %><br>
<% end %>
<% end %>
What is great about this too is that #calls_by_service does not contain ALL of the call_services, but instead only those call_service records associated with the calls specified in #call_ids. Exactly what I want at this level.
One Level Deeper which is where I am having trouble:
This is great but I need to go one level deeper: I want to display only the associated call_service_categories for the associated call_services of those selected calls specified by #call_ids.
In other words: I want to grab only the calls with the ids specified in #call_ids. Then: I want to eager load only the associated call_services for those grabbed calls. Then: I want to eager load only the associated call_service_categories for those grabbed calls.
A visual of the structure is like this:
So I want to be able to iterate through those associated call_service_categories (ex: 'Emergency Relief', 'Employment'), and then iterate through the associated call_services of those calls specified in the #call_ids, and then display those calls per service.
I figured out one level (by call_service), now I just need to figure out one level deeper (by call_service_category).
In the rails guides, I attempted looking at the section on specifying conditions on eager loaded associations. I was not having success, but I think the answer is in that section.
Any help is much appreciated. Thanks!
One of the belongs_to associations (in CallService or Call) should be actually a has_one (one-to-one relationship – belongs_to on the one side and has_one on the other). Apart from that, you can try the following code to produce a chained query with left joins and retrieve fields from all 3 tables:
CallServiceCategory.includes(call_services: :calls)
.where(calls: {id: #call_ids})
.references(:call_services, :calls)
I've noticed that you have a through association on your CallServiceCategory model, but as there would be no :call_services in includes, you can't reference fields from CallService model in references (you can, but they just won't appear in the actual sql query).
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!
I'm struggling to stretch my understanding of some basic Rails concepts beyond the tutorial examples I've done. I can't find any Q&A/docs/walkthroughs doing what I'm trying to do, so there's a good chance I'm going about this the wrong way.
I have a Team object with many Tags. The Team table has a few normalized fields, but most of the characteristics of the team are stored as Tags, i.e the Team 'Virginia Cavaliers' has Tags
{[tag_name => 'Conference', tag_value => 'ACC'],
[tag_name => 'Division', tag_value =>'I']}
etc. The db design was meant to accommodate many types of teams in the same table, with the tag table facilitating search for teams by arbitrary criteria.
So far so good. What I can't figure out is how to best access the team attributes given the team.
class Team < ActiveRecord::Base
belongs_to :sport
has_many :team_subscriptions
has_many :users, :through => :team_subscriptions
has_many :tags
def tagvalue
#Set up a hash to retrieve tag value by name?
#tagvalue = {}
tags.each do |t|
#tagvalue[t.tag_name] = t.tag_value
end
Rails.logger.info(#tagvalues.keys)
end
end
The hash is there but I can't access it in a view the way I'd like.
<%= #team.tagvalue["Conference"] %>
Is this sensible? possible? Thanks for your responses.
* Edited based on feedback (This site is awesome)*
The second suggestion is slick syntacticly, but has two hang ups I can see. I have to catch nulls as not all teams have all tags and sometimes they show up in the same list:
My clumsy implementation:
has_many :tags do
def [](key)
set = where(:tag_name => key)
if set.length > 0
set.first[:tag_value]
end
nil
end
end
The clean code thanks to edgerunner:
has_many :tags do
def [](key)
where(:tag_name => key).first.try(:tag_value)
end
end
And if I'm not wrong this method makes extra database calls every time I access a tag. The first method needs just one when the object is instantiated. Did I get both of those right?
There may be a different way to do the same. You can define an anonymous association extension and define the array accessor method for that to retrieve the tags with keys.
class Team < ActiveRecord::Base
...
has_many :tags do
def [](key)
where(:tag_name => key).first.try(:tag_value)
end
end
...
end
This will let you fetch only the required tags from the database instead of getting them all at once just to use one of them. It lets you do this:
<%= #team.tags["Conference"] %>
I have two tables: interests and Link_ui (this is for recording user and interest)
I want to input the user id and show all interests name that user have.
In Link_ui controller:
def output
#interests = LinkUi.find_by_sql [ 'SELECT interests.name FROM link_uis, interests
WHERE link_uis.interest_id = interests.id AND link_uis.user_id=? ', params['user_id'] ]
And input page:
<%= form_tag :action => 'output', :method => 'post' %>
enter id.
<%= text_field_tag ':user_id', '', 'size' => 30 %>
It comes out nothing, but I am sure there is matched data in database. And if I don't input parameter just set link_uis.user_id = 1, it comes out:
your search are [#<LinkUi >, #<LinkUi >, #<LinkUi >, #<LinkUi >]
What's wrong with this..
Well, find_by_sql on a LinkUi model expects you to return columns from the link_uis table, whereas you're selecting just interests.name. However, you are picking a bit of a fight with ActiveRecord, there. :)
You usually want to avoid find_by_sql, and instead let ActiveRecord generate your SQL for you. Probably most important for your example are associations.
The way I see it, you have a bunch of Users, and a bunch of Interests. Your LinkUis tie these two together (a LinkUi belongs to a User and an Interest). Feel free to correct me on this; this is your business logic as I gather from your example.
These classes (whose names I've emphasized) are your models, defined in the app/models directory. The assocations (relationships) between them should be defined on those classes.
Start of with a simple association in your User model:
class User < ActiveRecord::Base
has_many :link_uis
end
And in your Interest model:
class Interest < ActiveRecord::Base
has_many :link_uis
end
Then the LinkUi model that ties it together:
class LinkUi < ActiveRecord::Base
belongs_to :user
belongs_to :interest
end
Now, given any User, you can get his/her LinkUis by simply saying user.link_uis.all, and for each LinkUi, you can get the Interest as link_ui.interest. You can tell ActiveRecord to try and fetch these two in one shot as efficiently as possible using :include, and get a list of Interest names using the standard Ruby collect method. It then becomes:
user = User.find params['user_id']
link_uis = user.link_uis.all(:include => :interest)
interest_names = link_uis.collect { |link_ui| link_ui.interest.name }
You can take it one step further; for any User, you can directly get his/her Interests. Once you've set up the above associations, you can fold two ‘steps’ into one, like this:
class User < ActiveRecord::Base
has_many :link_uis
has_many :interests, :through => :link_uis
end
Which could turn the example into this one-liner:
interest_names = User.find(params[:user_id]).interests.collect { |i| i.name }