Rails db query to find and sort posts - ruby-on-rails

My app has designs that users can like (vote, using acts_as_voteable). To find a design's like count in the view, you use
#design.votes.count
I'm making a popular page to showcase the most popular designs based on the number of votes they have. I only want designs that has at least 5 votes to them. Right now, I had that in the view but I want to push that into the controller. My controller, thus far, looks like this which shows all the designs and sorts them in order of most votes.
def popular
#designs = Design.all
#designs.sort! {|t1, t2| t2.votes.count <=> t1.votes.count}
end
Now i just want to make sure the designs have a minimum vote count of 5.
Previously, I was doing this the wrong way and putting it in my view by putting this inside my Design loop
<% if design.vote.count > 5 %>
...
<% end %>
Thanks!

First of all, the behavior you want is better defined in the Design model and not in the controller since it deals with data. So in your Design model, add the following code:
scope :most_popular, -> do
results = select {|design| design.votes.count > 4 }
results.sort! {|t1, t2| t2.votes.count <=> t1.votes.count}
end
Adding the two scope methods above in your Design model, you could do this in your controller code:
def popular
#designs = Design.most_popular
end
Your controller code ends up being a lot cleaner and you have scope methods that you can reuse anywhere else you need them. :)
Hope that helps!

You can use a having() clause. See: http://guides.rubyonrails.org/active_record_querying.html#having
For example: Design.joins(:votes).group('votes.design_id').having('votes.count > 5').order('votes.count')
Edit
You can also just use a where clause. For example, for the first design:
Design.first.votes.where('count > 5')
Example for multiple designs:
Design.all.map{ |a| a.votes.where('count > 5').count }.sort! # returns a sorted array with all vote counts

Related

rubocop string interpolation and size condition

Before I except these two methods I wanted to see if anyone in the community had a better idea to structure these and make the cops pass. The first one with to_s seems a bit crazy too. I was thinking of refactoring the other method but that would be a single line or two.
Thoughts?
Code Examples One:
def destroy(resource_name, id)
delete "#{resource_name.to_s.pluralize}/#{id}"
end
Code Examples Two:
def all_products
products_map = fetch(:products).map { |x| [x['id'], x] }.to_h
variants = fetch :variants
variants.group_by { |x| x['product']['resource']['id'] }.to_a.map do |product_id, product_variants|
product.merge 'variants' => product_variants if product == products_map[product_id]
end.compact
end
For Code example One, maybe this can be used:
delete [resource_name.to_s.pluralize, id].join('/')
For Code example Two, yes you definitely need to refactor it.
Maybe you need to create a separate method that does all the grouping and merging, etc. for the variants part.
I am not sure if this is a good practice, but you can create a private method for it.

Fill array with nils for when nothing returned from query in ruby (RoR)

I have a model called foo with a date field.
On my index view, I am showing a typical "weekly view" for a specified week. To put the data in my view, I loop through each day of the specified week and query the data one day at time. I do this so that I can make sure to put a NIL on the correct day.
foos_controller.rb
for day in 0..6
foo = Foo.this_date(#date+day.days).first
#foos[day] = foo
end
index.html.haml
- for day in 0..6
%li
- if #foos[day].nil?
Create a new foo?
- else
Display a foo information here
Obviously, there's a lot of things wrong here.
I should find someone smart member to tell me how to write a good query so that I only have to do it once.
I should not have any if/else in my view
My goal here is to either show the content if the it is there for a particular day or show a "create new" link if not.
thanks for the help in advance!!
First, I have no idea what this_date actually does, but I'll assume it's retrieving a record with a specific date from your datastore. Instead of doing 7 queries, you can condense this into one using a date range:
Foo.where(date: (#date..(#date + 6.days)))
You can tack on a .group_by(&:date) to return something similar to the hash you are manually constructing, but using the actual dates as keys instead of the date offset.
To iterate over the dates in the view, I would recommend using Hash#fetch, which allows you to define a default return when a key is not present, e.g:
hash = { :a => 1, :b => 2 }
hash.fetch(:a){ Object.new } #=> 1
hash.fetch(:c){ Object.new } # #<Object:...>
The question now is what object to substitute for nil. If you want to avoid using conditionals here, I'd recommend going with the NullObject pattern (you could involve presenters as well but that might be a bit overkill for your situation). The idea here is that you would create a new class to substitute for a missing foo, and then simply define a method called to_partial_path on it that will tell Rails how to render it:
class NullFoo
def to_partial_path
"null_foos/null_foo"
end
end
You'll need to create partials at both app/views/foos/_foo.html.erb and app/views/null_foos/_null_foo.html.erb that define what to render in each case. Then, in your view, you can simply iterate thusly:
<% (#date..(#date + 6.days)).each do |date| %>
<%= render #foos.fetch(date){ NullDate.new } %>
<% end %>
Is this appropriate for your situation? Maybe it's also a bit overkill, but in general, I think it's a good idea to get in the habit of avoid nil checks whenever possible. Another benefit of the NullObject is that you can hang all sorts of behavior on it that handle these situations all throughout your app.

Rails3 - Multiple queries or single query in controller action

Hopefully a simple question around a rails best-practice.
Let's keep this super simple; say I have a task model that has an ID, description and status.
In my controller I have an index action to return all tasks
def index
#tasks = Task.all
end
My question is, in my view, suppose I want to display the tasks in separate HTML tables according to their status.
What is the best practice?
a) Query the database multiple times in the index action, ie
def index
#draft_tasks = Task.where(status: "Draft")
#approved_tasks = Task.where(status: "Approved")
#closed_tasks = Task.where(status: "Closed")
end
b) Query the database once, and filter in the contoller action
def index
tasks = Task.all
#draft_tasks = tasks.#somethinghere
#approved_tasks = tasks.#somethinghere
#closed_tasks = tasks.#somethinghere
end
c) Filter in the view
<% #tasks.each do |k, v| %>
<% some if statement searching for the status I want %>
# Some code to output the table
<%end%>
<%end%>
or
d) Something else?
The generally accepted best practices here are to keep controller methods thin and to keep logic out of the view. So with that in mind, one possible way to do this would be:
# model
class Task
scope :drafts, where(:status => "Draft")
scope :approved, where(:status => "Approved")
scope :closed, where(:status => "Closed")
end
# controller
def index
#draft_tasks = Task.drafts
#approved_tasks = Task.approved
#closed_tasks = Task.closed
end
This will make 3 queries to the database, which could become a performance concern down the road, but if that does happen, you can optimize it at the model level (e.g. by defining class methods drafts, approved, and closed where the first one called prefetches everything). It's less elegant though, so don't prematurely optimize.
This is a loaded question with no one best practice in my opinion. Given the case you have stated (display a table for each status) I would use the following thought process:
I would generally avoid case A when you're just dealing with one Model type. I try to limit the number of database queries when possible
Case B is what I would probably use if the view needs to display different markup depending on the status of a task.
I would usually tend towards case C if the markup is the same for each status. You can use the group_by function for this:
When the amount of information on your page starts to get larger and more complicated, you can start looking at extracting some logic out of the controller and into another object (common terms for this object would be a presenter or decorator). This can make testing some of your presentation logic easier by separating it from the controller and keeping your controllers 'thin'. But for the case you've given, I'd stick with option b or c.
In the simple case where the number of tasks is limited, I would do only a single query to retrieve them, and then separate them as follows:
tasks = Task.all
#draft_tasks = tasks.select { |x| x.status == 'Draft' }
#approved_tasks = tasks.select { |x| x.status == 'Approved' }
#closed_tasks = tasks.select { |x| x.status == 'Closed' }
Furthermore, depending on the bendability of your requirements, I would even render them in a single table with a clear visual marker what the state is (e.g. background-colour or icons). Then there would not even be a reason to separate the tasks beforehand (but I can imagine this would break your UI completely).
None of the above is valid once the number of tasks becomes larger, and you will need to apply pagination, and you need to display three different tables (one for each state).
In that case you will need to use the three separate queries as answered by #Ben.
Now UI-wise, I am not sure how you can paginate over three different sets of data at once. So instead I would use a single table showing all the states, and offer the option to filter on the status. In that case at least it is clear for the user what pagination will mean.
Just my two cents, hope this helps.
option a) seems better just because database can cache the query for you and stuff, so it should be faster.

Rails: decide to render spefic image in view based on database entry

I am relatively new to Rails and building a demo blackboard right now.
There are topics wich users can respond to. A posted opinion has a vote: positive, negative or neutral. These are selected by a checkbox.
In the topics show - view, you can see the already posted opinioins. For each opinion, there should be a thumb-up/down/neutral image rendered.
Right now, I am using HAML and formtastic for the new opinon form:
=f.input :vote, :as => :radio, :label => "Your vote", |
:collection => [["positive","/images/Thumbs-up-icon.png"],["negative","/images/Thumbs-down-icon.png"],["neutral","/images/Thumbs-neutral-icon.png"]] |
The important part of topic show looks like this:
- #opinions.each do |opinion|
[...]
#opinion_vote
= image_tag opinion.vote , {:height=>60}
As you can see, I directly save one of the three image_url ´s each time in the database for each opinion and directly use them to render the image in my topics view.
I know, this is bad on many levels, but it is the only working solution I found so far.
My idea was to save the votes as integers like 1 for positive, 0 for neutral and -1 for negative. Next stept would be to have a case in my topic view to decide which image should be renderen. But logic in view is bad again...
But what is the best way and place in Rails to decide what kind of image should be shown? The topic controller ? The opinion model ? A helper ? And finally: how could the code look ;) ?
Thank you.
This is where you would use a helper possibly combined with some model methods to make accessing the data simpler.
Opinion Model
def neutral?
if vote == 0
end
def positive?
if vote == 1
end
def negative?
if vote == 2
end
Opinion Helper
def vote_image
vote.positive? ? 'positive.png' : vote.negative? ? 'negative.png' : 'neutral.png'
end
As you've said, it's best to store things like this in integers rather than as text, because they are smaller to store and more efficient for index'ing your data.

How to create a sortable interface with 'acts as nested set' in RubyOnRails

I've been implementing some nice interactive interfaces that can sort lists in my m rails app for models that use acts_as_list. I have a sort function that gets called and sets the position for each record afterr each drag and drop using the sortable_element script.aculo.us function.
This is an example of the controller action that handles the sort after the drag and drop completes:
def sort
params[:documents].each_with_index do |id, index|
Document.update_all(['position=?', index+1], ['id=?', id])
end
end
Now I am trying to do this same thing with a model that is a nested set (acts_as_nested_set). An example of the type of interface interaction: http://script.aculo.us/playground/test/functional/sortable_tree_test.html
I am stuck on how to write the controller action to handle the sort when the drag and drop completes.
I've added the :tree=>true parameter to the sortable _element function so far which appears to send a list of hashes but it seems that I am still missing information about the entire nested order....
I was certain this has been done before and didn't want to try to reinvent the wheel, but I can't seem to find any examples of the controller action <-> view with js function setup to handle a sortable acts_as_nested_set
Any help with creating an interactive sortable nested set in rubyonrails would be appreciated!
Thanks,
John
a good solution with ONE sql-query from http://henrik.nyh.se/2008/11/rails-jquery-sortables
# in your model:
def self.order(ids)
update_all(
['ordinal = FIND_IN_SET(id, ?)', ids.join(',')],
{ :id => ids }
)
end
see example app here - http://github.com/matenia/jQuery-Awesome-Nested-Set-Drag-and-Drop
It's a hacky way of doing it, but its basically, sort first, then save order.
Uses nestedsortables, serializelist, and 2 actions to traverse the tree
PS: I know this question is over a year old but hoping that the link above helps someone else coming here.
edit: added Rails3 example with some slightly cleaner code.
Just found this:
sortable_element_for_nested_set on github
Looks like it'll do the job, however I'm having some bugs while trying to implement it. It basically makes the javascript return the id of the element that was moved, then goes through the elements and returns its new parent, left and right values. Can't believe it's taken this long for something like this to be written! Lucky it was just when I needed it :)
Here's a snippet of code from my project that does the trick:
http://gist.github.com/659532
def reorder_children(ordered_ids)
ordered_ids = ordered_ids.map(&:to_i)
current_ids = children.map(&:id)
unless current_ids - ordered_ids == [] && ordered_ids - current_ids == []
raise ArgumentError, "Not ordering the same ids that I have as children. My children: #{current_ids.join(", ")}. Your list: #{ordered_ids.join(", ")}. Difference: #{(current_ids - ordered_ids).join(', ')} / #{(ordered_ids - current_ids).join(', ')}"
end
j = 0
transaction do
for new_id in ordered_ids
old_id = current_ids[j]
if new_id == old_id
j += 1
else
Category.find(new_id).move_to_left_of(old_id)
current_ids.delete(new_id)
end
end
end
end
You call it on the parent, and it'll sort the children.
You just pass in the value that you get from Sortable, like so:
def reorder
#category.reorder_children(params[:categories])
render :nothing => true
end
Hope this helps.
//Lars
the_sortable_tree
Sortable Nested Set for Rails 3.1+
Dreaming about Drag and Drop for Nested Sets? It’s should be with JQuery?
Here’s the solution!

Resources