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.
Related
Lets say I have two models with a many to many relationship: Item and Property
Now I have an array of properties and I want to filter all items which properties match a given value (lets say a boolean value: property.value = true)
When I try
#items = Item.includes(:properties).where(:properties => {:id => [1,2,3].to_a, :value => true})
I would like to get all items where property(1) is true AND property(2) is true and so on. But with the code above I get all items related to the property id's and where any property is true. How should I change my code?
I would appreciate not to use a gem for this.
Looks like you are almost there:
property_ids = [1,2,3]
Item.joins(:properties).
where(:properties => { :id => property_ids, :value => true }).
group('items.id').
having('COUNT(properties.id) >= ?', property_ids.size)
joins does an INNER JOIN and is preferred over includes when you really need to join tables.
where is basically the conditions you already had, the only change is that there is not need to call to_a on the array.
Than you have to group to make that COUNT in SQL work.
having extracts the lines that have at least the expected number of property lines matching the condition.
I have Student model and I would like to get one record per term_id (one of the attributes).
Student.select(:term_id).distinct
works but the result is an array of term_ids, not the objects themselves - which is what I would like to get
Try this:
Student.pluck("DISTINCT id, term_id")
Student.select("DISTINCT term_id, `students`.* ")
Incase if you are using older versions of ruby (< 2.3.8),
Student.find(:all, :select => "DISTINCT(term_id), `students`.*")
or if you want id alone,
Student.find(:all, :select => "DISTINCT(term_id), id")
where students is your table name. You will get array of objects.
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'.
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.
I have a list of 'request' objects, each of which has fairly normal activerecord qualities. The requests table is related to the games table with a join table, 'games_requests,' so that a request has a request.games array.
The question is, is there a way to do a find for the last n unique requests, where uniqueness is defined by the games column and a couple others, but specifically ignores other colums (like the name of the requesting user?)
I saw a syntax like 'find (:all, :limit=>5, :include=>[:games,:stage])' but that was returning duplicates.
Thanks...
EDIT: Thanks to chaos for a great response. You got me really close, but I still need the returns to be valid request objects: the first 5 records that are distinct in the requested rows. I could just use the find as you constructed it and then do a second find for the first row in the table that matches each of the sets returned by the first find.
EDIT:
Games.find(
:all, :limit => 5,
:include => [:games, :requests],
:group => 'games, whatever, whatever_else'
)
...gives an SQL error:
Mysql::Error: Unknown column 'games' in 'group statement': SELECT * FROM `games` GROUP BY games
I made a few changes for what I assumed to be correct for my project; getting a list of requests instead of games, etc:
Request.find(
:all, :order=>"id DESC", :limit=>5,
:include=>[:games], #including requests here generates an sql error
:group=>'games, etc' #mysql error: games isn't an attribute of requests
:conditions=>'etc'
)
I'm thinking I'm going to have to use the :join=> option here.
Games.find(
:all, :limit => 5,
:include => [:games, :requests],
:group => 'games, whatever, whatever_else'
)
Try Rails uniq_by.It also works with association and returns array.
#document = Model.uniq_by(&:field)
More Detail
I think you'll be able to do this using find_by_sql and GROUP BY:
Games.find_by_sql("SELECT * FROM games GROUP BY user_id")