DRY instance variables for similar find queries - ruby-on-rails

Perhaps this question may have already been answered, but I am not sure what to search for.
I have a page with an A to Z list of clothing brands, which has an each block to iterate through them all. I would like to split this list out by letter, and have an A to Z row of links at the top, where each letter jumps down the page to their letter in the list. In order to do this however, I can only think of making an each loop for each letter, with <A NAME="A"> etc. next to it, and an instance variable for each one.
My question is, how do I avoid having 26 different instance variables in my controller?
#Abrands = Product.where('brand LIKE ?', "A%")
#Bbrands = Product.where('brand LIKE ?', "B%")
#Cbrands = Product.where('brand LIKE ?', "C%")
etc.
This is clearly not very DRY, is there a better way I could do this? I am still finding my feet with rails, any help would be much appreciated!

Would something like this work for you?
#products = Product.all.group_by{|product| product.brand.slice(0,1)}
This is a nice one-liner that will only issue 1 query. It will result in a hash similar to other users' suggestions.

# Hash initialization to empty arrays
#brands = Hash.new { |h,k| h[k] = [] }
Product.all.each do |product|
#brands[product.brand[0].upcase.to_sym] << product
end
which returns a hash like this:
{:A => [products for brands A*], :B => [products for brands B*], ...}
This method has the advantage of doing only one query instead of 26.

Does this openrailscasts episode help you?
Non Active Record (Products by Letter)

Related

RoR: How to sort an array with the help of scopes

I have an array #products. Each element of the array is a hash, containing a few fields (but not all) from Product table and the corresponding values.
I have a scope descend_by_popularity in Product which allows me to sort the products based on popularity field. I'd like to sort the array #products using this scope.
What I tried:
#product_group = Array.new
#products.each do |product|
#product_group.push(Product.find(product['id']))
end
#product_group1 = #product_group.descend_by_popularity
But this gives me error:
undefined method `descend_by_popularity' for #<Array:0xb2497200>
I also want to change the sorted Product list back to the format of #products array.
Thanks
Scopes only make sense within the ActiveRecord context for requests to the database (since it is used to change the SQL query). What you did is throwing a lot of products into an array. This array then knows nothing about the scope anymore. You would have to use the scope when you create the #products object. (and it does not seem to make a lot of sense to move the result of a query into an array)
So something like
#products = Product.descend_by_popularity.where(some more stuff)
should work for you. After that you should have the records in the order defined by the scope and can then either use them directly or still push them into an array if that's what you want to do.
With the updated info from the comments it looks like maybe the best way to go would be to first collect only the Product ids from the solr response into an array and then run that as search together with your scope:
#product_group = #products.map{|product| product.id}
#result = Product.where(id: #product_group).descend_by_popularity
this should technically work, peformance is a different question. I would consider aggregating this data into the Solr document, if it doesn't change too often.
Now assuming you are only interested in the order of products as such, you could do something like this to get #products into this order:
#result.map{|r| #products.find{|p| p[:id] == r.id}
though this may slow down things a bit.
Try this: find_by_id as params
#product_group = Array.new
#products.each do |product|
#product_group.push(Product.find(params['id']))
end
and return the array of #product_group
#product_group1 = #product_group.descend_by_popularity

Ruby - Order an Array

I have a table with 2 columns. "car" "year". I want to do a collect into an array of all cars and then sort them by year. How do I do that?
<% #cars_pre = Car.find(:all) %>
<% #cars = #cars_pre.collect {|x| x.car} %>
I need the steps to order the #cars array by year, keep in mind I am not keeping year values in the #cars array, just cars. Thanks in advance.
From the code sample, I'm guessing you are using Ruby on Rails. If you are, I would recommend you allow your database to order the results. For example:
#cars = Card.order("year").all
Use sort:
#cars.sort_by!(:year) # in-place sort
Why remove the information by which you wish to sort then sort it by that information? Just keep the information in the array–there's no need to remove it. Plus you need it for the sort.
That said, why not sort it on the DB side using order("year")?

How do I combine ActiveRecord results from multiple has_many :through queries?

Basically, I have an app with a tagging system and when someone searches for tag 'badger', I want it to return records tagged "badger", "Badger" and "Badgers".
With a single tag I can do this to get the records:
#notes = Tag.find_by_name(params[:tag_name]).notes.order("created_at DESC")
and it works fine. However if I get multiple tags (this is just for upper and lower case - I haven't figured out the 's' bit either yet):
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger'])
I can't use .notes.order("created_at DESC") because there are multiple results.
So, the question is.... 1) Am I going about this the right way? 2) If so, how do I get all my records back in order?
Any help much appreciated!
One implementation would be to do:
#notes = []
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger']).each do |tag|
#notes << tag.notes
end
#notes.sort_by {|note| note.created_at}
However you should be aware that this is what is known as an N + 1 query, in that it makes one query in the outer section, and then one query per result. This can be optimized by changing the first query to be:
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger'], :includes => :notes).each do |tag|
If you are using Rails 3 or above, it can be re-written slightly:
Tag.where("lower(name) = ?", "badger").includes(:notes) do |tag|
Edited
First, get an array of all possible tag names, plural, singular, lower, and upper
tag_name = params[:tag_name].to_s.downcase
possible_tag_names = [tag_name, tag_name.pluralize, tag_name.singularize].uniq
# It's probably faster to search for both lower and capitalized tags than to use the db's `lower` function
possible_tag_names += possible_tag_names.map(&:capitalize)
Are you using a tagging library? I know that some provide a method for querying multiple tags. If you aren't using one of those, you'll need to do some manual SQL joins in your query (assuming you're using a relational db like MySQL, Postgres or SQLite). I'd be happy to assist with that, but I don't know your schema.

rails where() sql query on array

I'll explain this as best as possible. I have a query on user posts:
#selected_posts = Posts.where(:category => "Baseball")
I would like to write the following statement. Here it is in pseudo terms:
User.where(user has a post in #selected_posts)
Keep in mind that I have a many to many relationship setup so post.user is usable.
Any ideas?
/EDIT
#posts_matches = User.includes(#selected_posts).map{ |user|
[user.company_name, user.posts.count, user.username]
}.sort
Basically, I need the above to work so that it uses the users that HAVE posts in selected_posts and not EVERY user we have in our database.
Try this:
user.posts.where("posts.category = ?", "Baseball")
Edit 1:
user.posts.where("posts.id IN (?)", #selected_posts)
Edit 2:
User.select("users.company_name, count(posts.id) userpost_count, user.username").
joins(:posts).
where("posts.id IN (?)", #selected_posts).
order("users.company_name, userpost_count, user.username")
Just use the following:
User.find(#selected_posts.map(&:user_id).uniq)
This takes the user ids from all the selected posts, turns them into an array, and removes any duplicates. Passing an array to user will just find all the users with matching ids. Problem solved.
To combine this with what you showed in your question, you could write:
#posts_matches = User.find(#selected_posts.map(&:user_id).uniq).map{ |user|
[user.company_name, user.posts.size, user.username]
}
Use size to count a relation instead of count because Rails caches the size method and automatically won't look it up more than once. This is better for performance.
Not sure what you were trying to accomplish with Array#sort at the end of your query, but you could always do something like:
#users_with_posts_in_selected = User.find(#selected_posts.map(&:user_id).uniq).order('username DESC')
I don't understand your question but you can pass an array to the where method like this:
where(:id => #selected_posts.map(&:id))
and it will create a SQL query like WHERE id IN (1,2,3,4)
By virtue of your associations your selected posts already have the users:
#selected_posts = Posts.where("posts.category =?", "Baseball")
#users = #selected_posts.collect(&:user);
You'll probably want to remove duplicate users from #users.

How to count occurrences of strings in objects

I'm looking to group, sort and count the occurrences of titles of a selection articles. I have selected my group of articles as follows:
#articles = Article.find(:all, :where("distance < ?", specified_distance))
Here out, I would like to group, sort and count the article titles that are included in #articles (not of all articles) for use in a view (as follows):
New York Yankees (3 Articles)
Boston Red Sox (1 Article)
Chicago Cubs (8 Articles)
This is not through usage of a many-to-many, it strictly string comparison, grouping, counting. I'm not sure how it is to be done. Is there a standard practice for doing this in Rails?
This is similar to what this user is asking for, but slightly different:
rails sorting blog posts by title
EDIT/
I must use #articles only (and not the physical query displayed above) because the query will often change and is much more complicated beyond that. So, my solution must refer to/from #articles.
In model:
scope :articles_up_to, lambda { |distance| where("distance < ?", distance) }
scope :article_counts_up_to, lambda { |distance|
articles_up_to(distance)
.select("COUNT(*) AS count, title")
.group("title")
.order("count DESC")
}
In controller:
#article_counts = Article.article_counts_up_to(specified_distance)
Edit:
This article on scopes may be helpful: http://edgerails.info/articles/what-s-new-in-edge-rails/2010/02/23/the-skinny-on-scopes-formerly-named-scope/index.html
Ok, I understand you want to use #articles, so briefly, here's a (really messy) solution which doesn't involve changing the model, or building new SQL queries:
#article_counts = #articles
.group_by(&:title)
.map { |a| {:title => a[0], :count => a[1].size} }
.sort { |a1,a2| a2[:count] <=> a1[:count] }
There's a group by method that you can use:
http://railscasts.com/episodes/29-group-by-month

Resources