I'm trying to achieve links in my page that allow me to change the order that my posts display in. Similar to 'Recent', 'Hot' and 'Oldest' on Reddit.
I currently have by default
PostsController.rb
def index
#posts = Post.order('created_at DESC').all.paginate(page: params[:page], per_page: 20)
end
How would I go about adding links to a method to reverse the flow of posts to ASC or to display posts descending by a specific column on the model like views?
Jon
I'd create some scopes and a method in the model to handle all the possibilities,
# Post.rb
scope :recent, -> { order(created_at: :desc) }
scope :hot, -> { order(something: :desc) }
scope :oldest, -> { order(created_at: :asc) }
def self.sort_by(sort_param)
case sort_param
when 'recent'
recent
when 'hot'
hot
when 'oldest'
oldest
else
all
end
end
# controller
#posts = Post.sort_by(params[:order]).paginate(page: params[:page], per_page: 20)
Since I'm whitelisting, I don't really need to sanitize, any wrong param will return the default order.
If you want you could use #send and add method names to the whitelist array, but you need to make sure that the scope exists
def self.sort_by(sort_param)
if %w(recent hot oldest).include? sort_param
send sort_param
else
all
end
end
Use will_paginate gem in your app for Sorting in both directions and Paginate.Visit this link to implement it https://github.com/mislav/will_paginate.
firstly you want to remove the all so that pagination works better.
def index
#posts = Post.order('created_at DESC').paginate(page: params[:page], per_page: 20)
end
then you can use a variable to control the order. The important thing is to sanitise before passing to mysql.
def index
sort_by = ActiveRecord::Base::sanitize(params.permit(:sort_by))
#posts = Post.order(sort_by).paginate(page: params[:page], per_page: 20)
end
Related
In my video#index I'm trying to order video objects by the most recent created_at time of their optionally present child element, comment, otherwise order by parent video element created_at.
I'm refactoring from this...
def index
if params[:tag]
#videos = Video.tagged_with(params[:tag]).page(params[:page]).per_page(10).order(created_at: "desc")
else
#videos = Video.all.page(params[:page]).per_page(10).order(created_at: "desc")
end
end
but unsure of how to structure the desired function.
The first step is in refactoring here is to create scopes in your model to DRY it out:
class Video
scope :newest, ->{ order(created_at: :desc) }
def self.paginate(page = 1, per_page: 10)
self.page(page).per_page(10)
end
end
You can compose scopes in rails by mutating variables:
#videos = Video.all
if params[:tag]
#videos = #videos.tagged_with(params[:tag])
end
Or by using .merge and .merge!:
#videos = Video.paginate(params[:page]).tap.do |s|
if params[:tag]
s.merge!(Video.tagged_with(params[:tag]))
else
s.merge!(Video.newest))
end
end
def index
#users = User.all.paginate(page: params[:page])
#users = User.named(params[:name]).paginate(page: params[:page]) if params[:name].present?
#users = User.countryname(params[:country]).paginate(page: params[:page]) if params[:country].present?
#users = User.gender(params[:gender_type]).paginate(page: params[:page]) if params[:gender_type].present?
end
The following code works fine if only :name or :country or :gender_type is present. But it does not work if multiple params are present. What is the DRY way of writing this code for multiple params? Obviously, I do not want to create a different line of code for each possible combination of params.
Here are the scopes:
class User
scope :countryname, -> (country) { where("country ILIKE ?", "%#{country}%")}
scope :gender, -> (gender_type) { where gender_type: gender_type}
scope :named, -> (name) { where("name ILIKE ?", "%#{name}%")}
If I have a query string of
example.com/users?name=sam&gender_type=male
it simply returns all users with names like sam and ignores their gender... I would need to code:
#users = User.gender(params[:gender_type]).named(params[:name]).paginate(page: params[:page]) if params[:gender_type] && params[:name].present?
but I do not want to have to write a new line of code for every single combination of parameters.
You could use the ruby try method. For example, you could write something like
#users = User.try(:gender, params[:gender_type]).try(:paginate, page: params[:page])
Look at try in api docs for other ways to use it.
the problem was the code should be
#users = User.all.paginate(page: params[:page])
#users = #users.named(params[:name]).paginate(page: params[:page]) if params[:name].present?
etc
Rails will then allow chain scoping automatically. the previous code creates separate independent non-chained instance variables.
I have a blog like rails app with posts. the posts can be order by most popular or newest. The most popular is the default order and the homepage, Id like to change this to be the newest order instead. Since Im still pretty new to rails I hired a programmer to do most of the work, However I know this is a pretty simple fix, I just don't know what to change. I think its something I need to change in either the post_controller or the post model, but If you nee to see something else let me know and ill put it up.
post.rb
class Post < ActiveRecord::Base
include UrlHelp
def self.highest_voted
self.order(:cached_votes_down)
end
end
post_controller.rb
def index
#posts = grab_correct_post
end
def grab_correct_post
if params[:sort_by] == "added_on"
#added_on = "sort-active"
Post.all.order("created_at DESC").paginate(page: params[:page], per_page: 7)
else
#most_popular = "sort-active"
Post.all.order(:cached_votes_score =>:desc).paginate(page: params[:page], per_page: 7)
end
If you look at the selector for swapping the sort order and get the sort_by value sent for most popular then you can change grab_correct_post to the following to change the default
def grab_correct_post
if params[:sort_by] == "<sort_by_value_for popular>"
#most_popular = "sort-active"
Post.all.order(:cached_votes_score =>:desc).paginate(page: params[:page], per_page: 7)
else
#added_on = "sort-active"
Post.all.order("created_at DESC").paginate(page: params[:page], per_page: 7)
end
end
It looks like you could reverse the order of the if-else blocks in the grab_correct_post method:
def grab_correct_post
if params[:sort_by] == "most_popular"
#most_popular = "sort-active"
Post.all.order(:cached_votes_score =>:desc).paginate(page: params[:page], per_page: 7)
else
#added_on = "sort-active"
Post.all.order("created_at DESC").paginate(page: params[:page], per_page: 7)
end
end
Note that I simply took a guess as to what the :sort_by parameter value should be for sorting by popularity, so be sure to change from "most_popular" to whatever you actually use.
I used Pull Review for reviewing my app's code and it came back with this:
Consider refactoring, similar code detected.
Occurred at:
SkillsController # index
PagesController # index
So the app/controllers/skills_controller.rb index action code is:
def index
#skill = Skill.new
if params[:search]
#skills = Skill.search(params[:search]).order('created_at DESC')
else
#skills = Skill.all.order('created_at DESC')
end
end
and on app/controllers/pages_controller.rb is:
def index
#users = User.all
if params[:search]
#users = User.search(params[:search]).order('created_at DESC')
else
#users = User.all.order('created_at DESC')
end
end
Am I suppose to somehow refactor these two actions on these two controllers? Also, I am not sure how I refactor this. Do I extract the if params[:search] segment and replace the instance variables with another variable that will be used on both actions?
Thanks for your time.
I don't know where your method search comes from. It seems it comes from a custom module/gem for ActiveRecord.
If so, you can change the method to shorten code in controller
def self.search(args)
return self unless args
original_search_logic args
end
# As well as extract order to a scope
scope :by_time, -> { order('created_at DESC') }
Then in controller:
# Skill
def index
#skills = Skill.search(params[:search]).by_time
end
# User
def index
#users = User.search(params[:search]).by_time
end
These should be dry enough for now.
take a look at the has_scope and inherited_resources. You can extract the params[:search] part with has_scope. And use inherited_resources to extract how to get the collection and do the ordering.
User has_many Tickets.
Ticket belongs_to User (ticket.user_id)
routes.rb
resources :users do
resources :tickets
end
rake routes
user_tickets GET /users/:user_id/tickets(.:format) tickets#index
users/index.html.erb
<%= link_to("View User's Tickets", user_tickets_path(user)) %>
users_controller.rb
private
def set_user
#user = User.find(params[:id])
#tickets = #user.tickets
end
tickets_controller.rb
def index
#search = Ticket.search(params[:q])
#tickets = #search.result.paginate(:page => params[:page], :per_page => 25)
render 'shared/tickets.html.erb'
end
When I hover over link, it shows .../users/[the selected user's id]/tickets
When it goes to the ticket/index page, it shows ALL tickets, not just the tickets with the selected user's id.
I'm pretty sure my route is incorrect, or it may be something else entirely. Any help is appreciated.
EDIT
I think my problem is that I need to call #tickets in the tickets_controller/index method a variety of ways, because I want to use that view for #tickets.all, #user.tickets, #facility.tickets, etc (to keep it DRY).
The same index list of tickets needs to change, based on the link from whence it came (whether it comes from the user list, showing a list of all tickets by that user, or from the facility list, showing a list of all tickets by that facility). I'm just doing something horribly wrong somewhere.
Possible solution I will try:
Maybe I need to create custom routes, like get 'users_tickets' => "users#users_tickets", then put the #tickets conditions in that method and call the shared/tickets.html.erb that way.
Sounds like you need to step through the association. Did you use
tickets_controller.rb
def index
#user = User.find(params[:user_id])
#tickets = #user.tickets
end
in the controller? If this doesn't help, can you post the controller code?
Aren't you trying to whittle down #tickets rather than do another query?
Right now you're redefining #tickets when they hit the tickets index, and it doesn't care that you defined #tickets as just belonging to that user on the users_controller. It just ignores that because you're using direct assignment in your tickets_controller index action. You probably want something like:
tickets_controller.rb
def index
#search = Ticket.search(params[q])
#tickets = #search.result.where(user: #user)
#tickets = #tickets.paginate(:page => params[:page], :per_page => 25)
end
Not tested, but I think that's more what you're wanting.
Ok - I think I need to review nested resources again, because I thought they did what I wanted automatically...or maybe they do, and I just don't get it yet.
In any case, I ended up creating a custom route and custom method in users:
routes.rb
get 'user_tickets' => "users#user_tickets"
users_controller.rb
def user_tickets
#user = User.find(params[:id])
#search = Ticket.where(:user_id => #user.id).search(params[:q])
#tickets = #search.result.paginate(:page => params[:page], :per_page => 25)
render 'shared/tickets.html.erb'
end
then, called:
<%= link_to("View Tickets", user_tickets_path(:id => user.id)) %>
I will do the same for facilities, departments, etc. Not sure this is the best way, but it works.
Thanks everyone for stimulating my brain cells.