For RESTful purpose, I made my routing just like this:
routes.rb
match 'shops/:sort/:genre/:area', :to => 'shops#index'
But what if genre was empty?
Isn't it going to redirect to example.com/shops/newest_first//california?
How can I solve this kind of routing and parameters problem?
Can anyone show me good example?
view
<%= form_tag shops_path, :method => :get do %>
<%= select_tag :sort, options_from_collection_for_select(Sort.all, 'id', 'name', params[:sort]), :prompt => "Newest first" %>
<%= select_tag :genre, options_from_collection_for_select(Genre.all, 'id', 'name', params[:genre]), :prompt => "all" %>
<%= select_tag :area, options_from_collection_for_select(Area.all, 'id', 'name', params[:area]), :prompt => "all" %>
<% end %>
Another View
While restful routing is the most intuitive and conventional, it does not always fit our needs.
In your case I'd suggest using query parameters instead of a strict restful route.
example.com/shops will receive 3 query parameters: sort, genre and area, so a URL may look like example.com/shops?area=california&sort=newest_first
The in the index action of you controller you can query for the existence of these parameters in the following manner:
def index
if !params[:sort].blank?
#shops = #shops.sort(params[:sort])
end
if !params[:area].blank?
#shops = #shops.where(:area => params[:area])
end
.
.
.
end
This way you are well protected against missing parameters in your controller, but still you are able to supply whatever data the user requests.
On a side note: be sure to check that the params hash you're using contains only values you are willing to accept.
Related
I've completed Hartl's Rails Tutorial and am now working on a project that uses the simple_calendar gem.
I want to add a dropdown to allow users to quickly navigate to a specific month and year without having to click the "next" link multiple times.
So far I have managed to create the dropdown form but I can't figure out how to set up the controller to get the url with the form parameters on submit.
This is in the partial I created that renders at the top of the monthview calendar:
<%= form_with do |form| %>
<%= select_month( Time.now, {:field_name => 'month', :id => 'month'} ) %>
<%= select_year( Date.today, {:start_year => Time.now.year,:end_year => Time.now.year+3} ,{:field_name => 'year', :id => 'year'} ) %>
<%= submit_tag("Go", :id=>"button", :class=>"date-button", :name=>"submit") %>
<% end %>
Can anyone explain how I can write the controller code to pass those parameters so that the user goes to a link like https://mycalendar.com/calendar?year=2021&month=june
Thank you for your help.
You can read the parameters directly
<%= form_with do |form| %>
<%= select_month( params[:month].present? ? Date.strptime(params[:month], '%B') : Time.now, {:field_name => 'month', :id => 'month'} ) %>
<%= select_year( params[:year].present? ? Date.strptime(params[:year], '%y') : Date.today, {:start_year => Time.now.year,:end_year => Time.now.year+3} ,{:field_name => 'year', :id => 'year'} ) %>
<%= submit_tag("Go", :id=>"button", :class=>"date-button", :name=>"submit") %>
<% end %>
But you have to add a checking for invalid date/parameters, so better do the parsing in the controller side
You can grab parameters sent to a controller via params[:parameter]. This works for both GET and POST requests. In a GET request these parameters are passed through the url, ie. /calendar?year=2021&month=june, whereas in a POST request they are passed in a formBody object.
One of the best things to do in Rails is to look at your console when a request is made. It will tell you the route, controller, action, and parameters in the log.
For a GET request you might use the method below to get the parameters.
# controller
def calendar
year = params[:year]
month = params[:month]
end
Generally a form would be sent via a PUT (create) or PATCH (update) request (known together as POST). In that case you might have a model object that the form fields would be stored in, and you would grab the parameters through a sanitization procedure. This sanitization is very important to ensure malicious code isn't passed through to your database.
def create
#event = Event.new(event_params)
...
end
private
def event_params
params.require(:event).permit(:month, :year, :title)
end
Your form_with might need a url: '/calendar' and even a method: :get to turn it into a GET request and send it to the correct route.
I am relatively new to Ruby on Rails, and am trying to set up a form with a new action on an existing controller.
My existing model and controller is called 'projects', and I created a new action in the controller called 'queue'. The goal is that a user can filter projects by different users using '/queue/username'.
routes.rb
match 'queue/:username' => 'projects#queue'
projects_controller.rb
def queue
if (params[:user_id].blank? && params[:user_id].nil?)
#projects = Project.find_all_by_user_id(User.where(:username => params[:username]).pluck(:id))
else
#projects = Project.find_all_by_user_id(params[:user_id])
end
end
queue.html.erb
<%= form_tag("queue", :id => "select_user", :method => "post") do %>
<%= hidden_field_tag('user_id', '') %>
<%= text_field_tag('user', nil, :placeholder => 'Enter a user...', class: "users",
data: {autocomplete_source: User.order(:lastname, :firstname).map { |u| {:label => u.firstname + " " + u.lastname, :id => u.id} }}) %>
<% end %>
When I submit this form it submits as 'queue/queue', and in order to have a direct link to this action I need to do:
<%= link_to "Queue", queue_path + "/" + current_user.username.to_s %>
Which I know is not correct.
My question is how do I get the form to submit as 'queue/username'? Should this be in a new 'queue' controller to handle routing separately? Any help is appreciated.
Rails version 3.2.13
Ruby version 1.9.3
Two places to revise:
Route. Better to use static route for POST without parameter, and specify POST
match 'projects/queue' => 'projects#queue', via: :post
Form tag. You need to specify the path
<%= form_tag "projects/queue", method: :post do %>
Better not to use div id, if you have to, use it like this
<%= form_tag "projects/queue", method: :post, html: {id: "select_user"} do %>
The answers from Josh and Billy can accomplish this well, to throw another hat into the mix I would suggest making this a route based off of the projects. Assuming your routes are restful and contains projects as resources projects:
resources :projects do
post "queue", :on => :collection
end
What this does is add the route projects/queue to your routes since it is based off of the collection instead of a member. See: http://guides.rubyonrails.org/routing.html#adding-more-restful-actions for more info.
It is worth noting why this happens. It is because the the route you are originally posting to is only queue when what you want is queue/:id since the id is specified via a url parameter you should use a get request. But since it looks like you are posting the id via a post request this is a little confusing. Choosing just to use a post request you do not need the url parameter like Billy suggested and I also agree since this will allow you to keep it a but more succinct.
Hope this helps!
Edit For username usage
Changing your form to:
<%= form_tag("queue", :id => "select_user", :method => "post") do %>
<%= hidden_field_tag(:username) %>
<%= text_field_tag('user', nil, :placeholder => 'Enter a user...', class: "users",
data: {autocomplete_source: User.order(:lastname, :firstname).map { |u| {:label => u.firstname + " " + u.lastname, :username => u.username} }}) %>
<% end %>
Assuming the JS is working as it should, you just need to populate the hidden field with the :username value instead of :id. Then on the controller side of things:
def queue
#projects = Project.find_all_by_username(params[:username])
end
Hope this makes sense!
You can change your route to something like
match 'queue/:username' => 'projects#queue', :as => 'projects_queue'
And then have the destination be
projects_queue(current_user)
I have my form_tag set up as follows:
<% Rating.find(:all, :conditions => ["recommendation_id = ? and rating_set = ? and product_id = ?", rec.id, params[:rating_set_id], params[:product_id]]).each do |rate| %>
<%= text_field_tag "recommendation_ratings[#{rec.id}][notes]", "#{rate.notes}", :id => "rec_note_text", :placeholder => 'Enter Notes..'%>
<% end %>
This works when the find conditions are met, however before the form is submitted, the recommendation_id is not persisted to the DB, so this find method does not return anything, and this causes my form tag not to render. It only render when all conditions of the find are met. How can I render my form regardless is the find condition is met?
You are using view/controller in a wrong way.
You should define new partial called _rating.html.erb
in there your form_tag (please replace with valid values, I have just put as an example)
<%= text_field_tag "recommendation_ratings[#{id}][notes]", "#{notes}", :id => "rec_note_text", :placeholder => 'Enter Notes..'%>
then, wherever you are rendering that list of Ratings, put for example in ratings/show.html.erb
<%= render #ratings%>
and in Ratings_controller you should put:
define show
#ratings = Ratings.find_all_with_conditions
end
and in model Rating.rb you should put:
define self.find_all_with_conditions
Rating..find(:all, :conditions => []) #put your logics here for finding all
end
I have just wrote just as an example how you should organize it, and I have not looked to put all valid parameters, I have put just for you to see how to organize you view.
I hope it will help.
I made my routing just like this. But what if genre was empty?
Isn't it going to redirect to example.com/shops/newest_first//california?
How can I solve this kind of routing and parameters problem??
routes.rb
match 'shops/:sort/:genre/:area', :to => 'shops#index'
view
<%= form_tag shops_path, :method => :get do %>
<%= select_tag :sort, options_from_collection_for_select(Sort.all, 'id', 'name', params[:sort]), :prompt => "Newest first" %>
<%= select_tag :genre, options_from_collection_for_select(Genre.all, 'id', 'name', params[:genre]), :prompt => "all" %>
<%= select_tag :area, options_from_collection_for_select(Area.all, 'id', 'name', params[:area]), :prompt => "all" %>
<% end %>
Another View
I would consider using GET params for things like area and sort since you are filtering indexes of other resources. You might also check the the section on Dynamic Segments in the guides, although that won’t help with the empty segment in the middle.
I have an Article resource and have defined resourceful routes for it. I want to create a simple page that shows the articles of the current user. I am aware that it is possible to do so by adding another action, for example 'search' to articles controller which will contain the custom code that searches for articles that have the same user id. And for the routes:
resources :articles do
get 'search'
end
But I'm not sure if adding a custom action is a good idea in this case. I'm thinking I can still use the index action (which shows all articles) and pass some sort of parameter from the url so that it can distinguish if the user wants to see all articles or just his own. But I'm not sure exactly how this can be done. Any help would be great. Thanks!
You can use the query string to pass parameters. see here
So you can pass something like .../articles?user_id=2
In your controller, just change the behavior according to the user_id parameter.
you don't need to create a new action/view for it.
You can add a small form to filter all articles or only my articles, for example:
<%= form_tag articles_path, method: :get do %>
<%= radio_button_tag :search, "all", :checked => true %>
<%= label_tag :all %><br />
<%= radio_button_tag :search, "my" %>
<%= label_tag :my_articles %><br />
<%= submit_tag "filter", name: nil %>
<% end %>
than in your controller:
def index
if params[:search] == 'my'
#articles = current_user.articles
else
#articles = Article.all
end