Filtering in Spree 3.1 - ruby-on-rails

I need product properties' filters on taxon's show page.
Heres's what i've added to my spree/taxon_controller.rb show method:
if params[:filter].present?
params[:filter].each do |k,v|
pp=#product_properties.where("property_id = ? and value in (?)", #properties.find_by(name: k), v).pluck(:id)
#products = #products.joins(:product_properties).where("spree_product_properties.id in (?)", pp)
end
end
I trying to do similiar query in console:
Spree::Product.joins(:product_properties).where("spree_product_properties.id in ?", [3,23]).where("spree_product_properties.id = ?", 4)
But it returned nothing.
If delete last where-statement, it's all ok
Spree::Product.joins(:product_properties).where("spree_product_properties.id in ?", [3,23])
One product object contain product_properties with id 3 and 4.
What i do wrong?
P.S. First i try to use spree's filter system with custom search as it write here, but it didn't work at all.
P.P.S I think filters is preetty stndart feature for e-shop, why it so many problems with them in spree?!

Related

Rails best way to get previous and next active record object

I need to get the previous and next active record objects with Rails. I did it, but don't know if it's the right way to do that.
What I've got:
Controller:
#product = Product.friendly.find(params[:id])
order_list = Product.select(:id).all.map(&:id)
current_position = order_list.index(#product.id)
#previous_product = #collection.products.find(order_list[current_position - 1]) if order_list[current_position - 1]
#next_product = #collection.products.find(order_list[current_position + 1]) if order_list[current_position + 1]
#previous_product ||= Product.last
#next_product ||= Product.first
product_model.rb
default_scope -> {order(:product_sub_group_id => :asc, :id => :asc)}
So, the problem here is that I need to go to my database and get all this ids to know who is the previous and the next.
Tried to use the gem order_query, but it did not work for me and I noted that it goes to the database and fetch all the records in that order, so, that's why I did the same but getting only the ids.
All the solutions that I found was with simple order querys. Order by id or something like a priority field.
Write these methods in your Product model:
class Product
def next
self.class.where("id > ?", id).first
end
def previous
self.class.where("id < ?", id).last
end
end
Now you can do in your controller:
#product = Product.friendly.find(params[:id])
#previous_product = #product.next
#next_product = #product.previous
Please try it, but its not tested.
Thanks
I think it would be faster to do it with only two SQL requests, that only select two rows (and not the entire table). Considering that your default order is sorted by id (otherwise, force the sorting by id) :
#previous_product = Product.where('id < ?', params[:id]).last
#next_product = Product.where('id > ?', params[:id]).first
If the product is the last, then #next_product will be nil, and if it is the first, then, #previous_product will be nil.
There's no easy out-of-the-box solution.
A little dirty, but working way is carefully sorting out what conditions are there for finding next and previous items. With id it's quite easy, since all ids are different, and Rails Guy's answer describes just that: in next for a known id pick a first entry with a larger id (if results are ordered by id, as per defaults). More than that - his answer hints to place next and previous into the model class. Do so.
If there are multiple order criteria, things get complicated. Say, we have a set of rows sorted by group parameter first (which can possibly have equal values on different rows) and then by id (which id different everywhere, guaranteed). Results are ordered by group and then by id (both ascending), so we can possibly encounter two situations of getting the next element, it's the first from the list that has elements, that (so many that):
have the same group and a larger id
have a larger group
Same with previous element: you need the last one from the list
have the same group and a smaller id
have a smaller group
Those fetch all next and previous entries respectively. If you need only one, use Rails' first and last (as suggested by Rails Guy) or limit(1) (and be wary of the asc/desc ordering).
This is what order_query does. Please try the latest version, I can help if it doesn't work for you:
class Product < ActiveRecord::Base
order_query :my_order,
[:product_sub_group_id, :asc],
[:id, :asc]
default_scope -> { my_order }
end
#product.my_order(#collection.products).next
#collection.products.my_order_at(#product).next
This runs one query loading only the next record. Read more on Github.

How can you conditionally build an ActiveRecord Query in Rails?

I'm trying to build a search results page in my rails app (still a noob) and I can't figure out how to build a query the rails way.
For example, if no parameters are present I want to return all results. If the user passes 1 to n optional parameters in the search form I want to add them to the query.
Next if they have a sort specified "price desc" or "year_built desc" or even a combination of both.
Finally use will_paginate to separate the results
# default to all listings
#listings = Mls.all
#listings.where("listing_price > ?", params[:listing_price]) unless params[:listing_price].blank?
# ... bunch of other search options ...
#listings.where("property_type = ?", params[:property_type]) unless params[:property_type].blank?
# order
#listings.order("some order by param") if some sort param
#listings.order("some order by param") if some other sort param
# paginate results
#listings.paginate(:page => params[:page])
Is there a "Rails" way of doing this?
Have you seen the (revised) Railscasts episode on advanced searches? Here is the link: http://railscasts.com/episodes/111-advanced-search-form-revised
The basic idea is to create a Search resource that will process the search params sent in through the form and in the background searches over the model in question (in your case Mls)
That way, instead of checking in the controller for the presence of certain parameters (e.g. params[:listing_price]) you could handle the conditions in your Search model (models/search.rb):
def find_listings
listings = Mls.order(:name)
listings = listings.where("listing_price > ?", listing_price) if listing_price.present?
# ... more condition checking
listings
end
A couple of links:
Using third-party gems (metasearch)
Code from scratch
Here is my scope examples, it will query if array is present
scope :tagged_one_of, -> (tags) { tags ? where("tags && ARRAY[?]::varchar[]", tags) : all }
scope :tagged_all_of, -> (tags) { tags ? where("tags #> ARRAY[?]::varchar[]", tags) : all }
example
Product.where(filter).where(sub_filter).tagged_one_of(tags_array)
More about scopes [here]

How to set default_scope order on attribute from another unassociated model

I have a Search resource that returns posts based on a filter as described in Railscast111, and have the following code:
def filter_posts
posts = Post.order('created_at DESC')
posts = posts.where("name ilike ?", "%#{keywords}%")
posts = posts.where(... #numerous other filters
posts
end
The filter itself seems to work fine. However, the content is not always returned in order of 'created_at DESC'. How can I sort the final output so that it's always in order of 'created_at DESC'? Currently, there is no association between the Post and Search models. Do I need to build one? If so, how?
Have you tried chaining the two conditions together?
posts = Post.where("name like?", "%#{keywords}%").order('created_at DESC')
Depending on how many filters you end up calling, you'll need to keep updating your original result, with the updated scope (based on your filter), as each time you use where it creates a new scope, instead of adjusting the original one. So you seem to be on the right path, as your original code does this, e.g
posts = Post.where("filter1")
posts = posts.where("filter2")
Have you tried sorting after all of the filters have been applied, so something like
posts = posts.order('created_at DESC')
or
posts = posts.sort_by &:created_at
Also, I'm not really sure what you mean by a Search resource, when (at least in this case) it appears you could keep the search logic within the Post model itself. Can you clarify, or maybe post the model?

Rails searching with multiple conditions (if values are not empty)

Let's say I have a model Book with a field word_count, amongst potentially many other similar fields.
What is a good way for me to string together conditions in an "advanced search" of the database? In the above example, I'd have a search form with boxes for "word count between ___ and ___". If a user fills in the first box, then I want to return all books with word count greater than that value; likewise, if the user fills in the second box, then I want to return all books with word count less than that value. If both values are filled in, then I want to return word counts within that range.
Obviously if I do
Book.where(:word_count => <first value>..<second value>)
then this will break if only one of the fields was filled in. Is there any way to handle this problem elegantly? Keep in mind that there may be many similar search conditions, so I don't want to build separate queries for every possible combination.
Sorry if this question has been asked before, but searching the site hasn't yielded any useful results yet.
How about something like:
#books = Book
#books = #books.where("word_count >= ?", values[0]) if values[0].present?
#books = #books.where("word_count <= ?", values[1]) if values[1].present?
ActiveRecord will chain the where clauses
The only problem is that if values[0] && values[1] the query would not return anything if values[0] was greater than values[1].
For our advanced searching we create a filter object which encapsulates the activerecord queries into simple methods. It was originally based on this Thoughtbot post
A book filter could look something like this:
class BookFilter
def initialize
#relation = Book.scoped
end
def restrict(r)
minimum_word_count!(r[:first]) if r[:first].present?
maximum_word_count!(r[:second]) if r[:second].present?
recent! if r.try(:[], :recent) == '1'
#relation
end
protected
def recent!
where('created_at > ? ', 1.week.ago)
end
def minimum_word_count!(count)
where('word_count >= ? ', count)
end
def maximum_word_count!(count)
where('word_count <= ?', count)
end
def where(*a)
#relation = #relation.where(*a)
end
end
#to use
books = BookFilter.new.restrict(params)
Take a look at the ransack gem, which is the successor to the meta_search gem, which still seems to have the better documentation.
If you do want to roll your own, there's nothing preventing you from chaining clauses using the same attribute:
scope = Book
scope = scope.where("word_count >= ?", params[:first]) if params[:first]
scope = scope.where("word_count <= ?", params[:last]) if params[:last]
But it's really not necessary to roll your own search, there are plenty of ready solutions available as in the gems above.

Using will_paginate with multiple models (Rails)

Pretty sure that I'm missing something really simple here:
I'm trying to display a series of pages that contain instances of two different models - Profiles and Groups. I need them ordering by their name attribute. I could select all of the instances for each model, then sort and paginate them, but this feels sloppy and inefficient.
I'm using mislav-will_paginate, and was wondering if there is any better way of achieving this? Something like:
[Profile, Group].paginate(...)
would be ideal!
Good question, I ran into the same problem a couple of times. Each time, I ended it up by writing my own sql query based on sql unions (it works fine with sqlite and mysql). Then, you may use will paginate by passing the results (http://www.pathf.com/blogs/2008/06/how-to-use-will_paginate-with-non-activerecord-collectionarray/). Do not forget to perform the query to count all the rows.
Some lines of code (not tested)
my_query = "(select posts.title from posts) UNIONS (select profiles.name from profiles)"
total_entries = ActiveRecord::Base.connection.execute("select count(*) as count from (#{my_query})").first['count'].to_i
results = ActiveRecord::Base.connection.select_rows("select * from (#{my_query}) limit #{limit} offset #{offset}")
Is it overkilled ? Maybe but you've got the minimal number of queries and results are consistent.
Hope it helps.
Note: If you get the offset value from a http param, you should use sanitize_sql_for_conditions (ie: sql injection ....)
You can get close doing something like:
#profiles, #groups = [Profile, Group].map do |clazz|
clazz.paginate(:page => params[clazz.to_s.downcase + "_page"], :order => 'name')
end
That will then paginate using page parameters profile_page and group_page. You can get the will_paginate call in the view to use the correct page using:
<%= will_paginate #profiles, :page_param => 'profile_page' %>
....
<%= will_paginate #groups, :page_param => 'group_page' %>
Still, I'm not sure there's a huge benefit over setting up #groups and #profiles individually.
in my last project i stuck into a problem, i had to paginate multiple models with single pagination in my search functionality.
it should work in a way that the first model should appear first when the results of the first model a second model should continue the results and the third and so on as one single search feed, just like facebook feeds.
this is the function i created to do this functionality
def multi_paginate(models, page, per_page)
WillPaginate::Collection.create(page, per_page) do |pager|
# set total entries
pager.total_entries = 0
counts = [0]
offsets = []
for model in models
pager.total_entries += model.count
counts << model.count
offset = pager.offset-(offsets[-1] || 0)
offset = offset>model.count ? model.count : offset
offsets << (offset<0 ? 0 : offset)
end
result = []
for i in 0...models.count
result += models[i].limit(pager.per_page-result.length).offset(offsets[i]).to_a
end
pager.replace(result)
end
end
try it and let me know if you have any problem with it, i also posted it as an issue to will_paginate repository, if everyone confirmed that it works correctly i'll fork and commit it to the library.
https://github.com/mislav/will_paginate/issues/351
Have you tried displaying two different sets of results with their own paginators and update them via AJAX? It is not exactly what you want, but the result is similar.

Resources