ActiveRecord - pre fetch single records from association - mysql

Consider the following models..
class Product < ActiveRecord::Base
has_many :pricings
end
class Pricing < ActiveRecord::Base
belongs_to :server
end
Pricing is a historical tracking table for prices of products, so there may potentially be hundreds of price points captured over time. What I want to add is a way to get only the current pricing for the product.
While i can add a has_one relation to the model like the following:
has_one :current_pricing, :class_name => "Pricing", :foreign_key => "product_id",
:order => 'created_at DESC'
This will fetch me all the Pricings for the current product before returning me only the first record.
I am looking for a way to accomplish this in a more efficient manner and was hoping that someone would have input or previous experience doing something similar.

You could use a named scope. Put this at the top of the Pricing model:
class Pricing < ActiveRecord::Base
named_scope :current, lambda { |product| { :conditions => { :product_id => product.id }, :order => 'created_at DESC', :limit => 1 } }
end
Then to get the current pricing of a product, you'd call "Pricing.current(product).first".
In addition to the named scope on pricing, you could add an accessor method to the Product model like so:
class Product < ActiveRecord::Base
def current_pricing
Pricing.current(self).first
end
end

You're looking for a named_scope.Define this in your Pricing model.Supply it with 1 parameter, the product_id.
named_scope :latest_price, lambda { |*args| {:conditions => {:product_id => args.first}, :order => "created_at DESC", :limit => 1} }
Excellent screencast on this by the way.

Add a :limit to the query?

Related

Reduce two SQL queries to one in activerecord maybe using joins

Two models User and Article have a relation as:
User has_one Article
Article belongs_to User
-User has a field called 'status'
-Article has a field called 'thing_i_need'
class User < ActiveRecord::Base
has_one :article, foreign_key: request_id
# status :integer
end
class Article < ActiveRecord::Base
belongs_to :user, foreign_key: request_id
# thing_i_need :string
end
Query: User.where(status: 'xyz').last.article.thing_i_need
Currently, two queries are fired to get the 'thing_i_need'
Any way to do it in one query ?
Does this help ?
You can try this
User.joins(:article).where(status: 'xyz').pluck('articles.thing_i_need').last
I'd recommend reading select & pluck query methods from the doc, earlier I was trying select in place of pluck and it didn't work.
Article.includes('user').where('users.status = ? and articals.request_id = users.request_id', 'xyz').first.thing_i_need
You may use user joins
Article.find(
:all,
:joins => :users,
:conditions => {
:users => {
:status => 'xyz'
}
}
).last.thing_i_need

find members with canceled subscriptions

So, Members have many subscriptions. A member is canceled if the last subscription canceled_at is not nil. But I can't seem to get the query to work right, to find all members with presently canceled subscriptions.
I'm doing this in the member model
scope :canceled, includes(:subscriptions).
where('subscriptions.canceled_at IS NOT NULL')
but it returns for users who have previously canceled subscriptions too. I need to use limit or something like that, I have the relation defined as
has_many :subscriptions, :order => "subscriptions.created_at DESC", :dependent => :destroy
so i can just work offf the .first, but not sure how to apply that logic to the class scope like that.
How about adding another association
Class Member < ActiveRecord::Base
has_one :last_subscription, :class_name => "Subscription", :foreign_key => "member_id", :order => "created_at desc"
Then try
scope :canceled, includes(:last_subscription).where('subscriptions.canceled_at IS NOT NULL')

:include searches with the wrong value

I have a number of models in a Rails project that are linked to a user, and I'm running into a loading problem I try to get all of the user's data displayed on a page.
My model declarations look sort of like this:
class User < ActiveRecord::Base
validates_presence_of :server_user_id
has_many :setup_notes, :foreign_key => "server_user_id"
has_many :closure_votes, :foreign_key => "user_id"
end
class SetupNote < ActiveRecord::Base
belongs_to :user, :foreign_key => "server_user_id"
end
I should note that in the SQL table for closure votes, user_id is the same value as server_user_id in the table for users.
When I try using the :include symbol in a Rails query, it ends up using user.id for the search value when I need to find closure votes and setup notes through user.server_user_id. In other words,
me = User.first(:conditions => ["server_user_id = ?", 12610], :include => :setup_notes)
generates the MySQL query
SELECT `setup_notes`.* FROM `setup_notes` WHERE (`setup_notes`.server_user_id = 1)
Which returns an empty set. Is there any way to format the Rails query/models to send the relevant SQL query, or do I need to write a raw query for all of the models associated with the users (and if that's the case, how do I do that?)
Thanks!
If I understand correctly (that User has a server_user_id field), in the User model you have to set the :primary_key option like so:
class User < ActiveRecord::Base
validates_presence_of :server_user_id
has_many :setup_notes, :foreign_key => "server_user_id", :primary_key => "server_user_id"
has_many :closure_votes, :foreign_key => "user_id", :primary_key => "server_user_id"
end

Rails 3 - Eager loading with conditions

Okay, I'm thoroughly stumped on this one. I'm trying to build a menu of published web pages organized by category.
Category.rb:
belongs_to :parent, :class_name => "Category", :foreign_key => "parent_id"
has_many :children, :class_name => "Category", :foreign_key => "parent_id"
has_many :pages, :documents, :galleries
Page.rb
belongs_to :category
The Page model also has :is_published, so I'm trying to filter on that as well. I am reluctant to post my feeble query attempts, but see no other solution than to beg much smarter people:
(self is #current_website)
self.categories.includes(:children, :pages).where('pages.is_published = 1')
This returns mostly what I need, but not Parent Categories without published pages. For instance, it works great if I have:
Parent Category
- Published Page
- Child Category
-- Published Page
Where it fails is when I have no published pages in the parent, like this:
Parent Category
- Child Category
-- Published Page
- Child Category
-- Published Page
Thanks in advance for any help on this. I'm trying to learn as much as I can about queries, but I'm against the wall on this.
UPDATE: Implementing KandadaBoggu's suggestion has yielded much better results, this was added to Category.rb
has_many :published_pages, :class_name => "Page",
:conditions => {:is_published => true}
However, when using the following:
self.categories.where(:parent_id => nil).includes({:children => :published_pages},
:published_pages)
I get the results I need, but I also get empty Parent Categories (no published_pages, no child categories with published pages. An example:
- Parent Category
-- Published Page
- Parent Category
-- NOTHING
My temporary fix was to appended the query with:
reject{|category| category.pages.empty? && category.children.empty?}
Thanks again for your help.
Add a new association called published_pages (apart from your current associations)
class Category
has_many :children, :class_name => "Category",
:foreign_key => "parent_id"
has_many :published_pages, :class_name => "Page",
:conditions => { :is_published => true }
end
Now you can get all the categories as follows:
self.categories.includes(:children, :published_pages)
If you are interested in learning why your approach didnt work, read the Rails documentation (scroll 10-15 lines after the Eager loading of associations section). I have included the relevant snippet below:
For example
Post.includes([:author, :comments]).where(['comments.approved = ?', true]).all
This will result in a single SQL query with joins along the lines of:
LEFT OUTER JOIN comments ON comments.post_id = posts.id and
LEFT OUTER JOIN authors ON authors.id = posts.author_id.
Note that using conditions like this can have unintended consequences.
In the above example posts with notion approved comments are not returned
at all, because the conditions apply to the SQL statement as a whole
and not just to the association. You must disambiguate column
references for this fallback to happen, for example :order =>
"author.name DESC" will work but :order => "name DESC" will not.
To eager load filtered rows of an association, use an association with conditions:
class Post < ActiveRecord::Base
has_many :approved_comments, :class_name => 'Comment',
:conditions => ['approved = ?', true]
end
Post.find(:all, :include => :approved_comments)

Use :include statement to find records

I'm working on Ruby On Rails 2.3.2 and I'm learning how to use the :include statement.
I'd like to get all the announcements order by their rate. I've got two models and tables for that: Announcement and Rate.
The models look like this:
class Announcement <
ActiveRecord::Base
belongs_to :rate
end
class Rate < ActiveRecord::Base
belongs_to :announcement
end
I'd like to do something like the following, but using :include statement.
Announcement.find_by_sql 'select * from announcements ann inner join rates r on ann.id = r.announcement_id order by r.average DESC'
I already tried this:
Announcement.paginate :page => params[:page], :per_page => 10, :include => [:rates], :order => 'rates.average DESC'
but it's trying to associate rates.id = announcements.id, instead of rates.announcement_id = announcement_id
How can I specify the correct relationship to make this work?
As Yuri points out in the comments, you have incorrectly defined your relationship.
Announcements should have_one :rate
As a named scope on announcment:
class Announcement < ActiveRecord::Base
has_one :rate
named_scope :order_by_rate, :include => :rate, :order => 'rates.average DESC'
end
Announcement.order_by_rate.paginate :per_page => 10, :page => params[:page]
Note the difference between :include and :joins as a find option. :joins will join the on the association(s) or SQL JOIN fragment given for purposes of complex WHERE/ORDER clauses. :include will only works on associations, it provides the same advantages of :joins, and also eager load the association.
Where eager loading makes a difference:
With include, #a.rate is populated in the same SQL statement that populates #a.
#a = Association.first :include => :rate
Without include #a.rate is not populated until required.
#a = Association.first
#a.rate #=> #a.rate is populated here, with a separate SQL statement.