Perform find on an array of hashes to avoid database find - ruby-on-rails

The find methods are very convenient for retrieving records, and I'm frequently using :include to prefetch referenced records to avoid expensive db accesses.
I have a case where I retrieve all sales by a salesperson.
#sales = Sales.find(:all,
:include => [:salesperson, :customer, :batch, :product],
:conditions => {:salesperson_id => someone},
:order => :customer_id)
I then want to slice and dice the returned records based on what was returned. For instance, I want to produce a report for all the sales made by this salesperson at a particular store, which we know is a subset of the previously returned data.
What I'd like to do is,
#storeSales = #sales.find_by_store(store_id)
...and retrieve this subset from the array held in memory as a new array, rather than achieve the same thing by performing a find on the database again. After all, #sales is just a array of Sales objects, so it doesn't seem unreasonable that this should be supported.
However, it doesn't seem that there's a convenient way to do this, is there? Thanks.

If you are using Rails 3, #sales will be an AREL criteria object. What you can do is as follows:
#sales = Sales.find(:all,
:include => [:salesperson, :customer, :batch, :product],
:conditions => {:salesperson_id => someone},
:order => :customer_id)**.all**
Now #sales is an instance of an Array of Sales model objects. Getting a subset of the array objects is now easy using the select method:
#my_product_sales = #sales.select { |s| s.product == my_product_criteria }
Upon using select method you will now have #sales being the full result set and #my_product_sales being the subset based on the collect criteria.

Related

Querying based on two associated records

I have a product that has many variants, those variants have two attributes: Size and Color.
I want to query for the Variant based on the two attributes I pass in - I got it to work with following:
variants = Spree::Variant.joins(:option_values).where(:spree_option_values => {:id => size.id}, :product_id => prod.id).joins(:option_values)
variant = variants.select{|v| v.option_values.include?(size)}
From my understanding, the select method more or less iterates through the array, which is kinda slow. I would rather have a query that finds the variant directly based on those two attributes.
I tried the following:
Spree::Variant.joins(:option_values).where(:spree_option_values => {:id => size.id}, :product_id => prod.id).joins(:option_values).where(:spree_option_values => {:id => color.id})
but this only ended up in returning an empty array.
How would I go about this?
Edit: Here are the product, variant and option_values models:
Product:
https://github.com/spree/spree/blob/master/core/app/models/spree/product.rb
Variant:
https://github.com/spree/spree/blob/master/core/app/models/spree/variant.rb
OptionValue: https://github.com/spree/spree/blob/master/core/app/models/spree/option_value.rb
OptionType: https://github.com/spree/spree/blob/master/core/app/models/spree/option_type.rb
Updated 2: you're right, this is not what you looking for.
So you can:
1) Build SQL subquery: (if joined table has size and has color at the same time then return TRUE). How quick it will be working - is a question...
2) Imagine you've created a model "ValuesVariants" for table "spree_option_values_variants" and kicked out habtm (replace with 2 has_manys + 2 has_manys through). Now you can search ValuesVariants with (option_type_id = size_id||color_id AND variant_id IN (array of product's variant ids)), extracting matched variants. It can be quick enough...
3) You can use :includes. so associated objects loaded into the memory and the second search do by array methods. In this case the concern is in memory usage.

will_paginate reporting too many entries and pages

I'm using will_paginate to display data returned from a query that includes both a joins and a select statement. When I paginate the data the number of entries is equal to the number of entries before executing the select statement, even though paginate is being called after the query, and the query contains fewer elements than paginate reports.
#sales = Sale.joins(:line_items).where(company_id: company_id, status: ['Complete', 'Voided'], time: (midnight_1..midnight_2)).order('id DESC')
puts #sales.length
14
#sales = #sales.select('distinct sales.*')
puts #sales.length
4
#sales.paginate(:per_page => 4, :page => params[page])
puts #sales.total_entries
14
This leads to displaying links to empty pages.
It's always going to be slightly harder to paginate and join in has_many or has_and_belongs_to_many associations with will_paginate, or indeed any pagination solution.
If you don't need to query on the joined in association you can remove it. You lose the benefit of getting the associated line items in one query but you don't lose that much.
If you need to query on it, and presumably you want sales that only have line items, you'll need to pass in a :count option to the paginate call which specifies additional options that are used for the call to count how many items there are. In your case:
#sales.paginate(:per_page => 4,
:page => params[page],
:count => {:group => 'sales.id' })
Assuming that your Sale model has_many :line_items, by joining you're going to get a 'sales' entry for every related 'line_item'.

Paginate through a randomized list of blog posts using will_paginate

I want to give users the ability to page through my blog posts in random order.
I can't implement it like this:
#posts = Post.paginate :page => params[:page], :order => 'RANDOM()'
since the :order parameter is called with every query, and therefore I risk repeating blog posts.
What's the best way to do this?
RAND accepts a seed in MySQL:
RAND(N)
From the MySQL docs:
RAND(), RAND(N)
Returns a random floating-point value
v in the range 0 <= v < 1.0. If a
constant integer argument N is
specified, it is used as the seed
value, which produces a repeatable
sequence of column values. In the
following example, note that the sequences of values produced by RAND(3) is the same both places where it occurs.
Other databases should have similar functionality.
If you use the SAME seed each time you call RAND, the order will be consistent across requests and you can paginate accordingly.
You can then store the seed in the user's session - so each user will see a set of results unique to them.
To avoid each page (generated from a new request) potentially having a repeated post you'll need to store the order of posts somewhere for retrieval over multiple requests.
If you want each user to have a unique random order then save the order in a session array of IDs.
If you don't mind all users having the same random order then have a position column in the posts table.
You could :order => RANDOM() on your original query that populates #posts, and then when you paginate, don't specify the order.
Create a named scope on your Post model that encapsulates the random behaviour:
class Post < ActiveRecord::Base
named_scope :random, :order => 'RANDOM()'
.
.
.
end
Your PostsController code then becomes:
#posts = Post.random.paginate :page => params[:page]

Filtering ActiveRecord queries by multiple unique attributes and combining results

In my rails project, I have a query which finds the 10 most recent contests and orders by their associated poll dates:
#contests = Contest.find(
:all,
:limit => "10",
:include => :polls,
:order => "polls.start_date DESC" )
Currently this shows each contest and then iterates through associated polls sorting the master list by poll start date.
Some of these contests have the same :geo, :office and :cd attributes. I would like to combine those in the view, so rather than listing each contest and iterating through each associated poll (as I'm doing right now), I'd like to iterate through each unique combination of :geo, :office and :cd and then for each "group," iterate through all associated polls regardless of associated contest and sort by polls.start_date. I'd like to do this without having to create more cruft in the db.
Unless I've misunderstood, I think you might be looking for this:
#contests.group_by { |c| [c.geo, c.office, c.cd] }
It gives you a Hash, keyed on [c.geo, c.office, c.cd], each entry of which contains an Array of the contests that share the combination.

Better Performance on Associations

Right now I have a table called Campaigns that has many Hits, if I call say:
Campaign.find(30).hits
Which takes 4 seconds, or 4213 ms.
If I call this instead:
campaign = Campaign.find(30)
campaign.hits.count
Does it still load all of the hits, then count? Or does it see I am counting and avoids loading all of the hits? (Which is currently 300,000+ rows).
I am trying to figure out a smart way to load/count my hits. I am thinking about adding a method to my Campaign.rb model, like:
def self.total_hits
find :first, :select => 'COUNT(id) as hits', :conditions => ["campaign_id = ?", self.id]
end
I know that query won't load from the hits table, but that is just an example of counting it from a self made query, apposed to Ruby on Rails doing this for me.
Would this memcache query be more effecient? (I have it running, but doesn't seem to be any better/faster/slower, just the same speed.)
def self.hits
Rails.cache.fetch("Campaign_Hits_#{self.campaign_id}", :expires_in => 40) {
find(:first, :select => 'COUNT(id) as hits', :conditions => ["campaign_id = ?", self.campaign_id]).hits
}
end
Any suggestions would be great!
How about:
Campaign.find(30).hits.count
You might also consider adding the following in hit.rb (assuming a one-to-many relationship between campaigns and hits).
belongs_to :campaign, :counter_cache => true
You then need a column in the campaigns table called hits_count. This will avoid hitting hits altogether if you're only getting the count.
You can check the API for the full rundown.
My ActiveRecord might be a little rusty, so forgive me if so, but IIRC Campaign.find(30).hits is at least two separate queries. How does Campaign.find(30, :include => [ :hits ]).hits do? That should perform a single query.

Resources