I want to order the Conversation model, using a custom method.
I found some solution:
How do you order by a custom model method that has no attribute in SQL?
and
http://awaxman11.github.io/blog/2013/10/11/sorting-a-rails-resource-based-on-a-calculated-value/ ,
but Conversation order have priority.
First- answer_percent desc,
second- order to last_answer time
(using custom model method last_answered_to_i ).
last_answered_to_i method source:
def last_answered_to_i
if Conversation.where(company_id: self.id, is_answered: true).present?
last_conversation = Conversation.where(company_id: self.id, is_answered: true).first
if last_conversation.answered_at.blank? || last_conversation.asked_at.blank?
minutes = (Time.now- last_conversation.updated_at)/1.minutes
else
minutes = (last_conversation.answered_at - last_conversation.asked_at)/1.minutes
end
minutes.to_i
else
nil
end
end
after ordering I want add pagination using kaminari gem.
#lists = Company.searchable.order("answer_percent desc").page(params[:page]).per(20)
How do I order by column and custom method and add pagination?
I think the answer depends on what you want to see in the view because some of the problem could actually be solved in how you call #lists there. Also, some of the links you found make sorting by a model method sound more difficult than it is.
In your case, you can sort your conversations by a custom method like so:
Conversation.all.sort_by(&:custom_method)
Or specifically:
Conversation.all.sort_by(&:last_answered_to_i)
Specifically, you cannot use SQL to sort or order by something not in the actual database, so you use the Ruby sort_by method. For more info on the ampersand, see this post.
For your actual view, I'm not sure really how you want to organize it. I recently did something where I needed to group my resource by another resource called "categories", and then sort the original resource by "netvotes" which was a custom model method, then order by name. I did it by:
Ordering by name in the controller: #resources = Resource.order(:name)
Grouping by category in the outer loop of the view: <% #resources.group_by(&:category).each do |category, resources| %>
Then sorting the resources by votes in the partial for resources: <%= render resources.sort_by(&:netvotes).reverse %>
The view is a bit confusing, so here is the full view loop in index.html.erb:
<% #resources.group_by(&:category).each do |category, resources| %>
<div class="well">
<h3 class="brand-text"><%= category.name %></h3>
<%= render resources.sort_by(&:netvotes).reverse %>
</div>
<% end %>
And here is the _resource.html.erb partial:
<div class="row resource">
<div class="col-sm-2 text-center">
<div class="vote-box">
<%= link_to fa_icon('chevron-up lg'), upvote_resource_path(resource), method: :put %><br>
<%= resource.netvotes %><br>
<%= link_to fa_icon('chevron-down lg'), downvote_resource_path(resource), method: :put %>
</div>
</div>
<div class="col-sm-10">
<%= link_to resource.name, resource.link, target: "_blank" %>
<p><%= resource.notes %></p>
</div>
</div>
I hope that helps you think through some more ways to address your problem.
Related
I'm currently learning Ruby on Rails thanks to a website named codecademy, and I'm learning how to display informations from a database's array stocked into a variable
The exercice's correction is as shown below :
<div class="main movie-show">
<div class="container">
<div class="movie">
<!-- Display the movie info here -->
<div class="info">
<%= image_tag #movie.image %>
<h3 class="movie-title"><%= #movie.title %></h3>
<p class="movie-release-year"><%= #movie.release_year %></p>
<p class="movie-plot"><%= #movie.plot %></p>
</div>
</div>
<h2>Cast</h2>
<% #actors.each do |actor| %>
<div class="actor">
<%= image_tag actor.image %>
<h3 class="actor-name"><%= actor.first_name %> <%= actor.last_name %></h3>
<p class="actor-bio"><%= actor.bio %></p>
</div>
<% end %>
</div>
</div>
You can see in the "movie" part that they directly take the variable to display the information needed, while they stock all the "actor" 's part in another |actor| variable
My question is the following, as I didn't find any satisfying answer online, is it possible to use two variables the same way in the same file ? Like using
<% #movies.each do |m| %>
and
<% #actors.each do |a| %>
Will it work anyway ? Will there be an error?
You can use as many number of instance variables as you want in your view provided that they are properly defined in your controller code.
If you have defined both #actors and #movies instance variables in your controller action, then you can access then them in corresponding view. Remember: I wrote, corresponding view.
There is other way as well. For example, if you have defined relation between your Movie model and your Actor model, and the relation states that a movie can have many actors. In that case, you only need to instantiate #movies in your controller, and then you can access actors in the following way:
<% #movies.each do |movie| %>
<% movie.actors.each do |actor| %>
<%# All other relevant code %>
<% end %>
<% end %>
In case, you don't know about relations, you can define them in following way:
class Movie < ActiveRecord::Base
has_many :actors
end
class Actor < ActiveRecord::Base
belongs_to :movie
# actors table should have a column named 'movie_id' for this to work
end
/products/index.html.erb
<div class="hide-for-small panel">
<h3>Sidebar</h3>
<h5 class="subheader">Feature Product</h5>
<% Product.random do | product | %>
<%= image_tag(Product.image_url) %>
<h5><%= link_to Product.title, product %></h5>
<p><%= button_to 'Add to Cart', line_items_path(:product_id => product) %></p>
<% end %>
</div>
/models/product.rb
def Product.random
self.limit(1).offset(rand(self.count)).first
end
Trying to pull a random product using Postgres. The query comes through in the console but I get no view results in the index.
Any solutions or different ways of accomplishing this?
The whole issue is you're passing a block to a method that doesn't take a block, so its silently ignored, and never executed, so the other problems you have don't come up.
All of your code sits inside a block which you're passing to Product.random via Product.random do |product|. That should be product = Product.random with no do/end block.
Once you've done this, you'll start seeing more errors as you're using Product.title instead of product.title etc.
It looks like your product in your image_tag and link are incorrectly referencing the model and not the random one.
Try changing these:
<%= image_tag(product.image_url) %>
<h5><%= link_to product.title, product %></h5>
You should really generate the random product in your controller and access an instance variable in your view.
Something like:
controller's index action:
#random_product = Product.random
view:
<%= image_tag(#random_product.image_url) %>
etc.
You should never access the model directly from the view.
First of all, you're mixing up class methods with instance methods. Try making the view:
<div class="hide-for-small panel">
<h3>Sidebar</h3>
<h5 class="subheader">Feature Product</h5>
<%= image_tag(#product.image_url) %>
<h5><%= link_to #product.title, #product %></h5>
<p><%= button_to 'Add to Cart', line_items_path(:product_id => #product.id) %></p>
</div>
Class methods (Product.random) are defined when you don't need a specific one. So product.title if for a specific product.
Second of all, in your controller for this action, you'll need to define the #product instance variable as #product = Product.random. And then you can use that in the view.
Third of all, getting a random record could be simplified to:
Product.order("RANDOM()").first
So your model code would look like:
def self.random
Product.order("RANDOM()").first
end
There is 'FoodType' model which are describes types of food in restaurants. I need to make view for creating a new restaurant, and I need to have list of checkboxes in order to allow user to setup types of food for each restaurant. I want to have something like this:
<% FoodType.all.each do |food_type| %>
...
<div class="row">
<%= f.check_box :food_types[0] %>
</div>
...
<% end %>
I want to have parameters like params[restaurant][food_types][0] = true in order to make some actions after creating. Please, tell me, how can I do it? Thanks in advance.
Presumably you have a join table which joins restaurants and food types? Let's say that you have one called restaurant_food_types (with a model RestaurantFoodType), which has restaurant_id and food_type_id?
You will then have this association in restaurants:
Restaurant < ActiveRecord::Base
has_many :restaurant_food_types
has_many :food_types, :through => :restaurant_food_types
This will give you the method .food_type_ids which you can call on a restaurant to set the joins. It's this method that you should hook into in your form: it expects an array of ids, so you need to set up an array-style parameter (one where the name ends in []) You may need to use check_box_tag rather than .check_box, to access an array-style parameter name: i would do this:
<% form_for #restaurant do |f| %>
<% FoodType.all.each do |food_type| %>
...
<div class="row">
<%= check_box_tag "restaurant[food_type_ids][]", food_type.id, #restaurant.food_type_ids.include?(food_type.id) %><%= food_type.name %>
</div>
...
<% end %>
<%= f.submit "Save" %>
<% end %>
Like i say i'm using a check_box_tag here but there might be a nicer way to hook into the food_type_ids method.
Previously I ordered my posts as this:
#posts = Post.find(:all, :order => "created_at DESC")
But now I want to replace created_at with a custom method I wrote in the Post model that gives a number as its result.
My guess:
#posts = Post.find(:all, :order => "custom_method DESC")
which fails..
It fails because you are asking your db to do the sorting.
#posts = Post.all.sort {|a,b| a.custom_method <=> b.custom_method}
Note that this becomes non-trivial when you want to start paging results and no longer wish to fetch .all. Think about your design a bit before you go with this.
Just to expand on #Robbie's answer
Post.all.sort_by {|post| post.custom_method }.reverse
As the first answer noted, order is an Active Record command that essentially does a SQL query on your database, but that field doesn't actually exist in your database.
As someone else commented, you can more cleanly run the Ruby method sort_by by using the ampersand (more info here):
Post.all.sort_by(&:custom_method)
However, things do get complicated depending on what you want to do in your view. I'll share a case I recently did in case that helps you think through your problem. I needed to group my resource by another resource called "categories", and then sort the original resource by "netvotes" which was a custom model method, then order by name. I did it by:
Ordering by name in the controller: #resources = Resource.order(:name)
Grouping by category in the outer loop of the view: <% #resources.group_by(&:category).each do |category, resources| %>
Then sorting the resources by votes in the partial for resources: <%= render resources.sort_by(&:netvotes).reverse %>
The view is a bit confusing, so here is the full view loop in index.html.erb:
<% #resources.group_by(&:category).each do |category, resources| %>
<div class="well">
<h3 class="brand-text"><%= category.name %></h3>
<%= render resources.sort_by(&:netvotes).reverse %>
</div>
<% end %>
And here is the _resource.html.erb partial:
<div class="row resource">
<div class="col-sm-2 text-center">
<div class="vote-box">
<%= link_to fa_icon('chevron-up lg'), upvote_resource_path(resource), method: :put %><br>
<%= resource.netvotes %><br>
<%= link_to fa_icon('chevron-down lg'), downvote_resource_path(resource), method: :put %>
</div>
</div>
<div class="col-sm-10">
<%= link_to resource.name, resource.link, target: "_blank" %>
<p><%= resource.notes %></p>
</div>
</div>
This is a bit more complicated than what I like but this I like to keep my sort to stay as a active record model so its bit more complicated than just
Post.all.sort_by {|post| post.custom_method }
what I do is:
ids = Post.all.sort_by {|post| post.custom_method }.map(&:ids)
Post.for_ids_with_order(ids)
this is a custom scope in the Post model
#app/models/post.rb
class Post < ApplicationRecord
...
scope :for_ids_with_order, ->(ids) {
order = sanitize_sql_array(
["position(id::text in ?)", ids.join(',')]
)
where(:id => ids).order(order)
}
...
end
I hope that this help
Well, just Post.find(:all) would return an array of AR objects. So you could use Array.sort_by and pass it a block, and since those records are already fetched, you can access the virtual attribute inside the block that sort_by takes.
RDoc: Enumerable.sort_by
Keep in mind that sort_by will return an Array, not an ActiveRecord::Relation, which you might need for pagination or some other some view logic. To get an ActiveRecord::Relation back, use something like this:
order_by_clause = Post.sanitize_sql_array(<<custom method expressed in SQL>>, <<parameters>>)
Post.all.order(Arel.sql(order_by_clause))
in rails 3 we can do this as: Post.order("custom_method DESC")
When upgrading app from rails2 to rails3
I am creating an application that highlights user messages from a stream based on whether or not the user has been 'vouched'. It works fine if it's setup for a single author. For example
controller: #vouch = Vouch.last.vouched_user_nickname
view:
<% Twitter::Search.new(params[:id]).each do |tweet| %>
<li>
<%= image_tag tweet.profile_image_url %>
<% if #vouch.include? tweet.from_user %> <div class="flit_message_containerh">
<u> <%= tweet.from_user %></u> <%= linkup_mentions(auto_link(h tweet.text)) %>
<div class="time_ago">
<%= link_to distance_of_time_in_words_to_now(tweet.created_at) , tweet %>
<% else %> <div class="flit_message_container">
<u> <%= tweet.from_user %></u>
<%= linkup_mentions(auto_link(h tweet.text)) %>
<div class="time_ago">
<%= link_to distance_of_time_in_words_to_now(tweet.created_at) , tweet %>
<% end %>
But I'm having trouble doing it for multiple user nicknames.
#vouch = Vouch.find(:all,
:select => "vouched_user_nickname",
:group => 'vouched_user_nickname'
)
Any ideas would be greatly appreciated. I'm a rails noob.
Assuming there isn't a relation between your Vouch model and the Twitter source (I haven't used that gem/plugin yet so I don't know), one solution is to pull all the Twitter entries you want and all the vouches in the controller and do the check in the view.
controller:
#tweets = Twitter::Search.new(params[:id])
#vouches = Vouch.find(:all)
view:
<% #tweets.each do |tweet| %>
<div class="flit_message_container<%=
#vouches.any? { |v| v.vouched_user_nickname == tweet.from_user } ? "h" : ""
%>">
...
</div>
<% end %>
#vouch = Vouch.find_by_vouched_user_nickname(:all, ["nickname1","nickname2"])
Your problem seems to be that you are not looping through the array, so how can it decide if certain elements meet the criteria you set?
Example, in your view:
<% for vouch in #vouch do %>
<% if vouch.include? tweet.from_user %>
<div class="flit_message_containerh">
<u> <%= tweet.from_user %></u> <%= linkup_mentions(auto_link(h tweet.text)) %>
<div class="time_ago">
<%= link_to distance_of_time_in_words_to_now(tweet.created_at) , tweet %>
<% else %> <div class="flit_message_container">
<u> <%= tweet.from_user %></u>
<%= linkup_mentions(auto_link(h tweet.text)) %>
<div class="time_ago">
<%= link_to distance_of_time_in_words_to_now(tweet.created_at) , tweet %>
<% end %>
<% end %>
I see several problems.
The first one is this:
#vouch = Vouch.last.vouched_user_nickname
You are using a variable called #vouch to store a user nickname. That is counterintuitive and will confuse other people reading your code (like myself). Use something like this instead:
#vouch = Vouch.last #on the controller
#vouch.vouched_used_nickname #on the view
This ... eum ... "exotic" naming convention helps confusing yourself when you try to do the "multiple" example:
#vouch = Vouch.find(:all,
:select => "vouched_user_nickname",
:group => 'vouched_user_nickname')
Activerecord's find(:all, ...) will allways return an array of activerecord objects (or an empty array). You seem to be expecting an array of strings. You will allways get Vouches if you do Vouch.find.
The :select part just limits the amount of information these vouches have (they only come with vouched_user_nickname populated. The rest, including their id, is empty, because it is not read from the database).
If you want to have an array of user nicknames you can do it like this:
# note the names. #vouchers in plural, and #nicknames for the user names
#vouchers = Vouch.find(:all, :select => "vouched_user_nickname",
:group => 'vouched_user_nickname')
#nicknames = #vouchers.collect{|v| v.vouched_user_nickname}
Is your problem that you don't know the correct controller code to write to find the #vouch array? Or is it that you don't know what to do with the array once you get it?
view: <% if #vouch.include? tweet.from_user %>
.include? is a method you can call on either a single object or an array of objects if tweet.from_user has an object that is also included in the #vouch array to get the #vouch array in your controller you should: