Storing values in Rails session - ruby-on-rails

I'm taking a course on Rails, and I need to "remember" the values selected by the user across https requests, in order to filter a list.
I am supposed to do this in the session 'Hash'.
Here is my index:
def index
#list all different values for the key ratings
#all_ratings = Movie.uniq.pluck(:rating)
#order movie list based on interaction with link from view
#movies = Movie.order(params[:sort_param])
#filter movies by rating, based on checkbox from view
#movies = #movies.where(:rating => params[:ratings].keys) if params[:ratings].present?
# I intend to store here the ratings selected, and then save in session
#selected_ratings = params[:ratings].present? ? params[:ratings] : [])
#It gives this error: undefined local variable or method `selected_ratings'
session[:selected_ratings] = #selected_ratings
end
Here my view:
%h1 All Movies
= form_tag movies_path, :method => :get do
Include:
- #all_ratings.each do |rating|
= rating
= check_box_tag "ratings[#{rating}]", #all_ratings, #selected_ratings.include?(rating)
= submit_tag 'Refresh'
%table#movies
%thead
%tr
%th{:class=> helper_class('title'), :id => ('title_header')}= link_to 'Movie Title', movies_path(sort_param: 'title')
%th Rating
%th{:class=> helper_class('release_date'), :id => 'release_date_header'}= link_to 'Release Date',movies_path(sort_param: 'release_date')
%th More Info
%tbody
- #movies.each do |movie|
%tr
%td= movie.title
%td= movie.rating
%td= movie.release_date
%td= link_to "More about #{movie.title}", movie_path(movie)
And now the questions:
When I render the debug in the view, and I choose one filter (PG in this case), I get this:
--- !ruby/hash-with-ivars:ActionController::Parameters
elements:
PG: G R PG-13 PG
ivars:
:#permitted: false
I don't quite understand that. I guess ivars are instance variables, but I don't know why the hash is not shown in a proper {hash}, and only with the values selected.
How do I use session[:selected_ratings] to remember the filter for the movies? I mean, how do I use session as a parameter.
Maybe:
#movies =#movies.where(:rating => session[selected_ratings].keys)
Where can I read about how to use session, how to store, how to access and use the params stored... I have read this, and that, and also some blogposts about sessions, authentication...
But I didn't manage to understand and apply to other situations than the ones described in the blog.

Why do I get the undefined variable error if selected_ratings is defined?
#selected_ratings is defined; selected_ratings is not. What you meant to do, I think, is something like:
#selected_ratings = (params[:ratings] || session[:selected_ratings] || [])
session[:selected_ratings] = #selected_ratings
Note that this is using a symbol, rather than an undefined variable.
Let's pretend session[selected_ratings] works, how do I use it to filter the movies? Maybe: #movies =#movies.where(:rating => session[selected_ratings].keys)
Well, what does #selected_ratings equal? I suspect it's probably an array of ratings, right? - Something like: ["PG", "PG-13", "R"]. In which case, ActiveRecord is clever enough and generating SQL, that you can just do something like:
#movies.where(rating: #selected_ratings)
Most importantly, where can I read about how to use session, how to store, how to access and use the params stored... I have read this, and that, and also some blogpost about sessions, authentication... But I didn't manage to understand and apply to other situation than the described in the blog.
That's a bit vague; I don't know what to suggest beyond the Rails documentation etc. for a beginners' overview. If you have a more specific question, I'd be happy to help.

Undefined error is because you refer to variable selected_ratings which isn't defined (you defined #selected_ratings, which isn't the same thing).
To answer your second question, you can do like so:
#movies = #movies.where(:rating => session[:selected_ratings])
session hash is easy to work with. You save and retrieve values just like you would with a regular hash.

Related

Select multiple values from associated model rails

Assuming I have this association
User have_many posts
Post belongs_to user
User Post
----------------
id id
name title
user_id
How to list only post title and username with includes/joins ?
(list of posts [title - username])
#posts = Post.includes(:user).select('........')
don't offer this
#posts = Post.all.each {|p| p.user.username}
__________________UP_____________________
It worked for joining 2 tables.
What if I want to use it for more complex example?
check out my prev question optimize sql query rails
#Humza's answer partly worked.
it might be something like this
#posts = Post.joins(:user, :category).paginate(:page => params[:page]).order("created_at DESC")
but It doesn't display posts that don't have category
I also need to display gravatar but I think I can just use user.email as usr_email and use gravatar_for (post.usr_email) but I'll have to customize gravatar helper for this.
posts_controller.rb
def index
#posts = Post.includes(:user).includes(:comments).paginate(:page => params[:page]).order("created_at DESC")
end
index.html.erb
<%= render #posts %>
_post.html.erb
<%= gravatar_for post.user, size:20 %>
<%= link_to "#{post.title}", post_path(post) %>
<%= time_ago_in_words(post.created_at) %>
<%= post.comments.count %>
<%= post.category.name if post.category %>
Take a look at pluck.
Post.joins(:user).pluck(:title, :name)
Note that it works in this case because there's no ambiguity regarding the name column, you might want to specify the table explicitly (pluck(:title, "users.name")).
includes is used in case of eager-loading. You need joins in this case.
posts = Post.joins(:user).select("posts.title AS title, users.name AS username")
You can access the values then in the following way:
post = posts.first
post.title # will give the title of the post
post.username # will give the name of the user that this post belongs to
If you can pluck multiple columns, then the following might be more useful to you:
posts = Post.joins(:user).pluck("posts.title", "users.name")
The result will be a 2D array, with each element being an array of the form [post_title, post_username]
Post.joins(:user, :category)
but It doesn't display posts that don't have category
That's because joins uses INNER JOIN to join the tables together. If you want to everything from Post even though the particular record doesn't have its counterpart in the other table, you need to use LEFT JOIN. Unfortunately ActiveRecord doesn't have a nice way of generating it and you will need to do that manually:
Post.joins("LEFT OUTER JOIN categories ON categories.post_id = posts.id")...
See A Visual Explanation of SQL Joins for more information.
You can call array methods on a scope so:
Post.includes(:user).map { |p| [p.title, p.user.name] }
will get the posts with included user and map each post to a tuple of the post title and the user name.
That may not entirely answer your question as I think you might want to restrict the results of the query to just the required fields in which case, I think you can add a .select('title', 'users.name') to the query. (Not in a position to test at the moment)

Looping through a list of checkboxes array to insert multiple record in ruby on rails 3.1 in controller

I have a list of check boxes created with check_box_tag (<%= check_box_tag "user_ids[]", user.id %>).
Now i want to loop through all the check-boxes based on users_ids in controller and insert data of all the users selected.
As Meltemi says, the usual way to iterate in ruby is .each. In your case, probably something like this, in the controller that receives the form:
params[:user_ids].each do |user_id|
u = User.find(user_id)
u.do_something_to_that_user #call a method or some such on the user
something_else.users << u #associate that user with something else
end
Alternatively, it can often be more efficient to do all of this in one go, though the exact form thereof depends on what you're doing with the user. For instance, if you want to associate the checked users with some record:
Record.user_ids = params[:user_ids]
Or if you want to update all of those users in some way:
User.where(:id => params[:user_id]).update_all(:attribute => some_value)

in Rails, with check_box_tag, how do I keep the checkboxes checked after submitting query?

Ok, I know this is for the Saas course and people have been asking questions related to that as well but i've spent a lot of time trying and reading and I'm stuck. First of all, When you have a model called Movie, is it better to use Ratings as a model and associate them or just keep Ratings in an array floating in space(!). Second, here's what I have now in my controller:
def index
#movies = Movie.where(params[:ratings].present? ? {:rating => (params[:ratings].keys)} : {}).order(params[:sort])
#sort = params[:sort]
#ratings = Ratings.all
end
Now, I decided to create a Ratings model since I thought It would be better. Here's my view:
= form_tag movies_path, :method => :get do
Include:
- #ratings.each do |rating|
= rating.rating
= check_box_tag "ratings[#{rating.rating}]"
= submit_tag "Refresh"
I tried everything that is related to using a conditional ternary inside the checkbox tag ending with " .include?(rating) ? true : "" I tried everything that's supposed to work but it doesn't. I don't want the exact answer, I just need guidance.Thanks in advance!
Update (the controller's method):
Here is the index method within my controller that is reading this hash - for clarity. I apologize for the fuzziness before.
def index
#all_stores = Product.all_stores
#selected_stores = params[:stores] || session[:stores] || {}
if #selected_stores == {}
#selected_stores = Hash[#all_stores.map {|store| [store, store]}]
end
if params[:stores] != session[:stores]
# session[:stores] = #selected_stores
session[:stores] = params[:stores]
redirect_to :stores => #selected_stores and return
end
#products = Product.order("created_at desc").limit(150).find_all_by_store(#selected_stores.keys).group_by { |product| product.created_at.to_date}
. . . etc
Couple of things
1) .order(params[:sort]) opens you up for sql injection attacks,
someone could potentially delete all of your users by putting this in the query string
http://localhost:3000/movies?sort=email%3B+DELETE+from+users+--
see rails 3 activerecord order - what is the proper sql injection work around?, this problem does not exist with .where, rails will sanitize the input for where method
2) minor thing, AREL delays making the actual call to the db until you iterate on the collection, so you can 'build' up queries, this syntax might be easier to understand then using ternary operator?
#movies = Movie.order(xxxxxx)
#movies = #movies.where(:rating => params[:ratings].keys) if params[:ratings].present?
3) for your ratings, like cdesrosiers says constant is fine, then your view
Movie::RATINGS.each do |rating|
check_box_tag "ratings[]", rating # <input type="checkbox" name="ratings[]" value="PG" />
EDIT: maintain selected values
# controller
#selected_ratings = (params[:ratings].present? ? params[:ratings] : [])
# view
Movie::RATINGS.each do |rating|
check_box_tag "ratings[]", rating, #selected_ratings.include?(rating)
# <input type="checkbox" name="ratings[]" value="PG" checked="checked" />
note the [] in the naming of the checkbox, this will give you params[:ratings] as an array in your controller action
#movies = #movies.where("rating IN (?)", params[:ratings]) if params[:ratings].present? and params[:ratings].any?
some links
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-include-3F
http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-check_box_tag

How can I set a parameter using link_to in rails?

In a haml file I have an element such as the following:
%th Movie Title
that I am trying to turn into a clickable element that will sort the column it is the header for. So far I've worked out that this may look something like the following
%th= link_to "Movie Title", "foo"
except I need to set a parameter and instead of going to "foo" I want to just reload the current page with the list of movies sorted in the controller (though separate research, my guess is that this can be done via something like:
def index
#movies = Movie.find(:all, :order => params[:sort])
end
Can someone give me some advice on what I should do about the link_to call? Is anything I've written above way off? Thanks.
Here you go :)
= link_to 'Movie Title', request.parameters.merge({:sort => "title ASC"})

Ruby on Rails: Sorting collection of objects

I have a method that will provide an array of model object. Some of those model's attributes are easy to sort by using some help from SQL. I mean, using .find(:condition => {})
The problem is some of the other attributes isn't. It has to be calculated, modified, or did something else before showing. Then, I found the way to sort it using collection sorting. Which is, collection.sort{|a,b| a.something_to_sort <=> b.something_to_sort}
OK! That's works. But the problem is, is it possible to make that something_to_sort part to become a dynamic variable? For example, I want to take a parameter from the UI and assign it to that something_to_sort like following,
HTML
<select name="sort">
<option value="name">Name</option>
<option value="age">Age</option>
<option value="activity.category">Activity</option>
<option value="tax.calculate_personal_income_tax">Income Tax</option>
<option value="tax.calculate_withholding_tax">Withholding Tax</option>
<option value="doctor.name">Doctor's Name</option>
</select>
Rails
params[:sort] ||= "Age"
#people = Person.find(:all)
#people.sort{|a,b| a.params[:sort] <=> b.params[:sort]} #Note that this is an incorrect syntax
Is there any way to do this by not having to write a sort block for each of sorting option?
Additional #1
I've just tried this and it was working for some sorting option
#people.sort{|a,b| a.send(params[:sort]) <=> b.send(params[:sort])}
This works for name and age as they are people's attribute (and it works for peopls's method too). But for the association's such as tax.calculate_personal_income_tax, it's not.
So, my colleague told me to create new people's method, calculate_personal_income_tax and then change the option value from tax.calculate_personal_income_tax to calculate_personal_income_tax so that the send method will be able to find this method ...
def calculate_personal_income_tax
tax.calculate_personal_income_tax
end
But, this is not really what I want. Because I believe that there must be a way to access this association method without having to define a new model method. If I choose to do this and one day people model become larger, more attribute, more information to sort and display. Then, there will be a lot of method like this.
The other reason is, if I have to define a new method, that method should have to do some logic, calculation or else, not just retrieve a data from other model.
Additional #2
Finally, I've just found the way to access other model attribute by adding args to the find method, :join and :select.
#people = Person.find(:all, :select => "persons.*, tax.tax_rate AS tax_rate", :joins => :tax, :condition => {some conditions})
The result of that will be a table of person joining with tax. Then, I use the send(params[:sort]) thing to help me sorting the attribute that I want.
#people.sort {|a, b| a.send(params[:sort]) <=> b.send(params[:sort])}
params[:sort] ||= "age"
method_chain = params[:sort].split(".")
#people = Person.find(:all)
#sorted_people =
#people.sort_by do |person|
method_chain.inject(person) do |memo,method|
memo.send(method)
end
end
This assumes that the values passed in params[:sort] are always valid method names which can be chained together in the order specified. You'd want to keep in mind that anything can be passed in the params hash, so this would be very unsafe if exposed to untrusted users (e.g. params[:sort] = "destroy". Oops.)
Also, if you can do this in SQL instead you'll get better performance.
In fact, the more I look at this, the more it seems like a bad idea.
I have a loot system that is doing this on a calculated value, that is based on a reference table which can change. As the value is calculated, it could come from anywhere.
My players/index.html.rb looks something like.
<h1>Players</h1>
<table>
<tr>
<th>Name</th>
<%= generate_headings(params) %>
</tr>
<% players.each do |player| %>
<tr>
<td><%= player.name %></td>
<% LootType.all.each do |loot_type| %>
<td><%= player.loot_rate(loot_type.name) %></td>
<% end %>
<tr>
<% end %>
</table>
The loot_rate function is calculating on two related tables (player.raids and player.items) as "player raids / loot of type + 1".
The generate_headings function is in players_helper.rb, and it looks like:
def generate_headings(params = {})
sort = params[:sort]
headings = ""
LootType.all.each do |loot_type|
heading_text = "#{loot_type.name} Rate"
if (sort == loot_type.name)
column_heading = "<th>#{heading_text}</th>"
else
heading_url = "/players?sort=#{loot_type.name}"
column_heading = "<th>#{link_to heading_text, heading_url}</th>"
end
headings += column_heading
end
headings.html_safe
end
In my index action of players_controller.rb, I have:
def index
sort = params[:sort]
if !sort
#players.sort! { |a,b| a.name <=> b.name }
else
#players.sort! { |a,b| b.loot_rate(sort) <=> a.loot_rate(sort) } # Reverse sort (for my purposes)
end
respond_to do |format|
format.html
format.json {render :json => #players }
end
end
This means that when I add or remove "loot types" in my system, and the player list automagically allows users to sort by those types.
Long winded answer, but hope it helps.

Resources