activerecord find through association - ruby-on-rails

I am trying to retrieve an activerecord object from my db. My models are
class User < ActiveRecord::Base
belongs_to :account
has_many :domains, :through => :account
end
And
class Account < ActiveRecord::Base
has_many :domains
has_many :users
end
And
class Domain < ActiveRecord::Base
belongs_to :account
end
Now I would like to retrieve a user based on the username and a domain name (lets assume that these are attributes of the User and the Domain classes respectively). i.e. something along the lines of
User.find(:first, :conditions =>{:username => "Paul", :domains => { :name => "pauls-domain"}})
I know that the above piece of code will not work since I do have to mention something about the domains table. Also, the association between users and domains is a one-to-many (which probably further complicates things).
Any ideas on how should this query be formed?

If you're using Rails 3.x, the following code would get the query result:
User.where(:username => "Paul").includes(:domains).where("domains.name" => "paul-domain").limit(1)
To inspect what happen, you can append .to_sql to above code.
If you're using Rails 2.x, you'd better write the raw sql query.

The following piece of code did the trick:
User.joins(:account).joins('INNER JOIN "domains" ON "accounts"."id" = \
"domains"."account_id"').where(:users => {"username" => "Paul"}).
where(:domains => {"name" => "paul-domain"})
Sorry about the formatting of this long line of code

Related

How to query a collection through multiple associations?

I need to collect objects that are all connected through multiple layers of associations, and I don't know who to do so.
I need to get a collection of CustomText based on a string query param.
Basically I need to do a query that will pull a collection of CustomText by name:
#searched_content = params[:search].downcase
#query = CustomText.where("lower(name) like ?", "%#{#searched_content}%")
but then also filters the #query to only search LineItems that have been a part of an approved order. I am using Spree, where Spree::Order has_many Spree::LineItem. Basically, doing something like this (doesn't work at all, but hopefully you'll be able to see what I'm trying to do):
#query = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"}).joins(:custom_texts).where("lower(name) like ?", "%#{#searched_content}%"))
Models:
class CustomText < ActiveRecord::Base
belongs_to :custom_set, :inverse_of => :custom_texts
end
class CustomSet < ActiveRecord::Base
belongs_to :spree_line_item, :class_name => Spree::LineItem, :foreign_key => :spree_line_item_id
has_may :custom_texts, :dependent => :destroy, :inverse_of => :custom_set
end
class LineItem < ActiveRecord::Base
has_many :custom_texts, :through => :custom_sets
has_many :custom_sets, :dependent => :destroy, :foreign_key => :spree_line_item_id
end
Any help would be very appreciated.
Well, this was just a simple syntax error with one too many end parenthesis at the end...stupid spelling error.
#query = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"}).joins(:custom_texts).where("lower(name) like ?", "%#{#searched_content}%")

Rails 5 how to form association between tables on multiple shared attributes

In Rails 5, given a relationship between two tables that involves joining them on multiple shared attributes, how can I form an association between the models corresponding to these tables?
SQL:
SELECT *
FROM trips
JOIN stop_times ON trips.guid = stop_times.trip_guid AND trips.schedule_id = stop_times.schedule_id
I tried the following configuration, which works in general...
class Trip < ApplicationRecord
has_many :stop_times, ->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) }, :inverse_of => :trip, :primary_key => :guid, :foreign_key => :trip_guid, :dependent => :destroy
end
class StopTime < ApplicationRecord
belongs_to :trip, :inverse_of => :stop_times, :primary_key => :guid, :foreign_key => :trip_guid
end
Trip.first.stop_times.first #> StopTime object, as expected
Trip.first.stop_times.first.trip #> Trip object, as expected
... but when I try to use it in more advanced queries, it triggers ArgumentError: The association scope 'stop_times' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported....
Trip.joins(:stop_times).first #=> the unexpected ArgumentError
StopTime.joins(:trip).first #> StopTime object, as expected
I understand what the error is referencing, but I'm unsure of how to fix it.
EDIT:
I was hoping a single association would be sufficient, but it has been noted two different associations can do the job:
class Trip < ApplicationRecord
has_many :stop_times,
->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) },
:primary_key => :guid,
:foreign_key => :trip_guid # use trip.stop_times instead of trip.joined_stop_times to avoid error about missing attribute due to missing join clause
has_many :joined_stop_times,
->{ where("stop_times.schedule_id = trips.schedule_id") },
:class_name => "StopTime",
:primary_key => :guid,
:foreign_key => :trip_guid # use joins(:joined_stop_times) instead of joins(:stop_times) to avoid error about instance-specific association
end
Trip.first.stop_times
Trip.eager_load(:joined_stop_times).to_a.first.joined_stop_times # executes a single query
If anyone reading this knows how to use a single association, please at-mention me.
I don't think it is the right solution, but it can help. You can add another similar instance independent association that will be used for preloading only. It will work with :joins and :eager_load but not with :preload.
Note that :includes might internally use either :eager_load or :preload. So, :includes will not always work with that association. You should explicitly use :eager_load instead.
class Trip < ApplicationRecord
has_many :preloaded_stop_times,
-> { where("stop_times.schedule_id = trips.schedule_id") },
class_name: "StopTime",
primary_key: :guid,
foreign_key: :trip_guid
end
# Usage
trips = Trip.joins(:preloaded_stop_times).where(...)
# ...
# with :eager_load
trips = Trip.eager_load(:preloaded_stop_times)
trips.each do |trip|
stop_times = trip.preloaded_stop_times
# ...
end

How to return associated model objects in Rails with ActiveRecord

Let's assume I have the following three objects:
class Filing < ActiveRecord::Base
belongs_to :company
has_many :people , :dependent => :destroy
end
class Company < ActiveRecord::Base
has_many :filings
end
class Person < ActiveRecord::Base
belongs_to :filing
end
I'm attempting to make a single request that returns the filing with it's associated company and an array containing it's associated people. This is being used for an API request of GET filing/:id
I saw the documentation on joining tables with ActiveRecord, however when I run the following query:
Filing.joins(:people,:company)
It doesn't appear to include the associated people or company in the result set. I'm kind of confused about why I would do a join if the associated data won't be returned. What am I missing here? What query should I be running?
Update
As mentioned in the comments. I want to be able to generate the following output:
{ "filing" => { "filing_id" => 123, "company" => { ... }, "people" => [{"person_id" => 1}, {"person_id" => 2}] } }
With the help of commenters I ended up finding the answer. All that needs to be called is:
Filing.find( id , :include => [:company,:people] )
I think what you want is the following:
Filing.includes([:company, :people]).where(:filing => [:id => 756])

Rails, ActiveRecord: how do I get the results of an association plus some condition?

I have two models, user and group. I also have a joining table groups_users.
I have an association in the group model:
has_many :groups_users
has_many :users, :through=> :groups_users
I would like to add pending_users which would be the same as the users association but contain some conditions. I wish to set it up as an association so that all the conditions are handled in the sql call. I know there's a way to have multiple accessors for the same model, even if the name is not related to what the table names actually are. Is it class_name?
Any help would be appreciated, thanks
Use named_scopes, they're your friend
Have you tried using a named_scope on the Group model?
Because everything is actually a proxy until you actually need the data,
you'll end up with a single query anyway if you do this:
class User < ActiveRecord::Base
named_scope :pending, :conditions => { :status => 'pending' }
and then:
a_group.users.pending
Confirmation
I ran the following code with an existing app of mine:
Feature.find(6).comments.published
It results in this query (ignoring the first query to get feature 6):
SELECT *
FROM `comments`
WHERE (`comments`.feature_id = 6)
AND ((`comments`.`status` = 'published') AND (`comments`.feature_id = 6))
ORDER BY created_at
And here's the relevant model code:
class Feature < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :feature
named_scope :published, :conditions => { :status => 'published' }
This should be pretty close - more on has_many.
has_many :pending_users,
:through => :groups_users,
:source => :users,
:conditions => {:pending => true}
:pending is probably called something else - however you determine your pending users. As a side note - usually when you see a user/group model the association is called membership.
In the User model:
named_scope :pending, :include => :groups_users, :conditions => ["group_users.pending = ?", true]
That's if you have a bool column named "pending" in the join table group_users.
Edit:
Btw, with this you can do stuff like:
Group.find(id).users.pending(:conditions => ["insert_sql_where_clause", arguments])

Grouping Activerecord query by a child attribute

Is it possible to use an attribute of a child to group a query?
Post.find(:all, :include => [ :authors, :comments ], :group=>'authors.city')
does not work.
However, I am able to use author.city as part of the conditions.
The solution is to force the necessary join so that ActiveRecord can resolve "authors.city":
Post.find(:all, :include => [ :author, :comments ], :joins=>"INNER JOIN authors ON posts.author_id=authors.id", :group=>'authors.city')
If that's what you're using, then the syntax is wrong for the :group argument, it should be:
Post.find(:all, :include => [ :author, :comments ], :group=>'authors.city')
Make sure your :author and :comments associations are correct. If 'authors' is the actual table name, then you'll need a 'has_one :author' association in you Post model, and an Author model.
Associations need to be correct, too:
class Post < AR:Base
belongs_to :author
has_many :comments
end
class Author < AR:Base
has_many :posts
end
class Comment < AR:Base
belongs_to :post
end
And the db schema:
posts
id
author_id
authors
id
comments
id
post_id
This will let the query run correctly, however, now I'm getting an error with the results... the :group clause doesn't seem to be applied when :include is used.
Have a look at the query that is generated in your log file - you can often paste the query into your favourite MySQL tool to get a more detailed error.
You might actually need to provide an aggregate function to get the database to group correctly (this happens in MySQL rather than a syntax error sometimes).
Should the author include be pluralized?
Post.find(:all, :include => [ :authors, :comments ], :group=>'authors.city')

Resources