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 }
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 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 have a Gift model:
class Gift
include Mongoid::Document
include Mongoid::Timestamps
has_many :gift_units, :inverse_of => :gift
end
And I have a GiftUnit model:
class GiftUnit
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :gift, :inverse_of => :gift_units
end
Some of my gifts have gift_units, but others have not. How do I query for all the gifts where gift.gift_units.size > 0?
Fyi: Gift.where(:gift_units.exists => true) does not return anything.
That has_many is an assertion about the structure of GiftUnit, not the structure of Gift. When you say something like this:
class A
has_many :bs
end
you are saying that instance of B have an a_id field whose values are ids for A instances, i.e. for any b which is an instance of B, you can say A.find(b.a_id) and get an instance of A back.
MongoDB doesn't support JOINs so anything in a Gift.where has to be a Gift field. But your Gifts have no gift_units field so Gift.where(:gift_units.exists => true) will never give you anything.
You could probably use aggregation through GiftUnit to find what you're looking for but a counter cache on your belongs_to relation should work better. If you had this:
belongs_to :gift, :inverse_of => :gift_units, :counter_cache => true
then you would get a gift_units_count field in your Gifts and you could:
Gift.where(:gift_units_count.gt => 0)
to find what you're looking for. You might have to add the gift_units_count field to Gift yourself, I'm finding conflicting information about this but I'm told (by a reliable source) in the comments that Mongoid4 creates the field itself.
If you're adding the counter cache to existing documents then you'll have to use update_counters to initialize them before you can query on them.
I tried to find a solution for this problem several times already and always gave up. I just got an idea how this can be easily mimicked. It might not be a very scalable way, but it works for limited object counts. The key to this is a sentence from this documentation where it says:
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
So, get this done, you can define a class function like so:
def self.with_units
ids = Gift.all.select{|g| g.gift_units.count > 0}.map(&:id)
Gift.where(:id.in => ids)
end
The advantage is, that you can do all kinds of queries on the associated (GiftUnits) model and return those Gift instances, where those queries are satisfied (which was the case for me) and most importantly you can chain further queries like so:
Gift.with_units.where(:some_field => some_value)
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.
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"] %>