I have a slightly complex navigational system with numerous landing pages, multipage forms, and multiple ways of accessing the standard CRUD functions.
Objective: To maintain a variable such as (params[:target]) throughout the system such that each controller knows where to redirect a user based on the location and circumstances of the link_to.
How to implement this the best way?
Is there a better way to store navigation markers so any controller and method can access them for the current_user?
If using params[:target] is a good way to go (combined with if or case statements in the controller for the redirect), how do I add the target params to the form when adding or editing a record? For example, view says:
# customers/account.html.erb
<%= link_to "edit", :controller => "customers", :action => "edit", :id => #customer.id, :target => "account" %>
# customers/edit.html.erb
<%= submit_tag "Update", :class => "submit" %>
# how to send params[:target] along with this submit_tag so the update method knows where to redirect_to?
Thank you very much.
I think you could get the same result by setting a session[:target] each time is necessary. so you'll always know where to redirect from controllers without changing link_to params and leaving clean URLs.
hope this helps,
a.
Related
What is the 'Rails way' to provide access to methods such as the following from a view
def approve!
self.update_attribute status, 'approved'
end
Is it best to create a link to a custom route
<%= link_to 'Approve', approve_object_path(#object) %>
#objects_controller.rb
def approve
#object.approve!
end
Or to create an update form
<%= simple_form_for #object do |f| %>
<%= f.input :status, input_html { value: 'approved' }, as: :hidden %>
<%= f.submit %>
<% end %>
On the one hand, using a form and not using the approve! method at all seems to align better with restful routes.
On the other hand, a link to a custom route seems to provide less opportunity for submitted values to be manipulated by the user, and also requires less code to implement.
Which is the preferred way to do this?
I don't know if there's a preferred best practice, per se...
Just my opinion, but I normally do the link_to approach, and for an "state machine" like your example. The need for an entire form for a simple action like this is a lot of extra code that isn't necessary when an action can be called to change the state.
The counter argument to this is that it breaks CRUD, and requires a non-CRUD route. Convention over configuration champions would probably prefer an entire new controller to change the state of the object.
TL;DR - I do the link_to approach, and I use :remote => true to make it asynchronous so the page doesn't even reload (unless you need the page to redirect elsewhere).
You can change state remotely with both the scenarios.
But I think if only a state has to be changed then use link_to. As we don't need to have form features with listed attributes in params here.
My view contains this link to redirect to the index page for people.
<%= link_to 'No', '/people' %>
I want to send an extra flag to the index method of the people controller so it will do something extra in case this link is clicked. I've tried a ton of different things, none of which work. I tried using the more complicated syntax with :controller => :people_controller, :action => :index...but since i'm coming from the show view it sends the ID and messes up my routes.
How can i send an extra parameter with this link_to?
<%= link_to 'No', people_path(extra_parameter: "Veg") %>
Rails newbie here...please be kind...
I have an app that generates a unique paragraph of text (sampled from arrays and made unique based on criteria entered in _form.html.erb, rendered from the criteria/new page). On the criteria/show page, I have 2 links, one to generate a new unique paragraph using the same criteria, and one to return to the form and edit the criteria.
For the second link, I want to retain the criteria previously entered, as the user may want to only change one entry and not have to re-enter all of it. I've found plenty of information regarding ajax calls, respond_to, remote: true, but haven't found this exact answer or if I have, my brain is TIRED and failed to comprehend.
I've seen the suggestion in a similar question (How to come back to form without reset the values?) which talks about repopulating the data: "Just add the parameters (/myForm?param1=1¶m2=2¶m3=3) to the button url that leads back to the search form. Then populate the fields using the parameters. No need to use session variables." Sadly, I'm unclear about how to do implement this.
Would someone please be so kind as to walk me through either (preferably the simplest!) way of doing this?
Current links on show page (commented things are those that I tried unsuccessfully:
<%= link_to 'Generate Another!', criteria_path %> #regenerates text with current criteria
<%= link_to 'Enter New Information', new_criterium_path %> #this is the link I'm trying to change
<%#= link_to 'Enter New Information', new_criterium_path, :remote => true %>
<%#= link_to 'Enter New Information', { :controller => 'Criteria', :action => "new" } :remote => true %>
Controller new action (commented until I can make it work):
def new
#criterium = Criterium.new
#testing ajax stuff
# respond_to do |format|
# format.html
# format.json
# end
end
There's lots of talk about needing a new.js.erb file (or would that be show.js.erb?) and a div tag in my show(?) page but again, brain TIRED. I've attempted some of these things, then deleted from the code so as not to gak things up too much and be unable to remember what I did.
Please tell me what else you need from me in order to answer. Many thanks in advance!
Looks like you need to pass back params to your 'new' action, and then use them when instantiating Criterium:
<%= link_to 'Enter New Information', new_criterium_path(criteria: params[:criteria]) %>
def new
#criterium = Criterium.new(criteria: params[:criteria])
end
An apartment_listing has many reviews, and a review belongs to an apartment_listing.
In the file views/apartment_listings/show.html.erb, I show a list of reviews for that particular apartment_listing. These reviews are generated with the partial view apartment_listings/_review.html.erb like so:
<%= render :partial => "review", :collection => #apartment_listing.reviews %>
In _review, I want to have a button that, when pressed:
Increments that review's helpful_count attribute.
Makes it so that it cannot be pressed again while in the same browser - probably using cookies.
I feel like the former shouldn't be too hard to figure out, but it's got me beat. I'm really not sure where to start with the second goal.
EDIT: I managed to update the review's helpful_count attribute with this code in apartment_listings/_review.html.erb:
<%= form_for review, :method => :put, :remote => true do |f| %>
<%= f.hidden_field :helpful_count, value: (review.helpful_count + 1) % >
<%= f.submit 'Helpful?' %>
<% end %>
However, I'm not sure if this is the best way to do it, and I'd like to be able to disable the button after it is clicked.
Your code for updating helpful_count has the potential for problems. Imagine two users have loaded an apartment on their web page. One of them marks it helpful, and the next one does as well. Since when they initially loaded the page, helpful_count was the same, after both of them click helpful, the count will only be incremented by one: it would be updated twice to the same value.
Really, you want to create a new action, probably under the reviews resource for an apartment. That action could use ActiveRecord's increment method to update the helpful_count (technically there's still a race condition in increment!, you'd encounter it much less often) http://apidock.com/rails/ActiveRecord/Persistence/increment%21
Cookies seem like a reasonable solution for the latter problem. Simply bind to submit on the form with jQuery, and create the cookie in the handler.
What does the code look like in your reviews controller? More experienced RESTful coders might be able to speak more coherently on this, but the way I see it, incrementing the helpful_count attribute should be an action sent to the reviews controller. That way, you can create a link that performs the action asynchronously.
For example, inside _review.html.erb:
<% collection.each do |review| %>
<%= link_to "Mark as Helpful", "/apartment_listing/#{#apartment_listing.id}/reviews/#{#review.id}/incHelpful?nonce=#{SecureRandom.rand(16)}", :remote => true, :method => :put %>
# ... Do something cool with your review content ...
<% end %>
Inside your ReviewsController class:
def incHelpful
unless params[:nonce] == session[:nonce][params[:id]]
#review = Review.find(params[:id])
#review.helpful_count += 1
#review.update_attributes(:helpful_count)
session[:nonce][params[:id]] = params[:nonce]
end
render :nothing
# Optionally return some javascript or JSON back to the browser on success/error
end
Inside /config/routes.rb:
put "apartment_listing/:apart_id/reviews/:id/incHelpful" => "reviews#incHelpful"
The main idea here is that actions that edit a resource should use the PUT http method, and that change should be handled by that resource's controller. Rails' built-in AJAX functions are engaged by setting :remote => true inside the link_to helper. The second concept is that of a nonce, a random value that is only valid once. Once this value is set in the user's session, subsequent requests to incHelpful will do nothing.
Say I have an Article model, and in the article 'new' view I have two buttons, "Publish" and "Save Draft".
My question is how can I know which button is clicked in the controller.
I already have a solution but I think there must be a better way.
What I currently used in the view is:
<div class="actions">
<%= f.submit "Publish" %>
<%= f.submit "Save Draft", :name => "commit" %>
</div>
So in the controller, I can use the params[:commit] string to handle that action.
def create
#article = Article.new(params[:article])
if params[:commit] == "Publish"
#article.status = 'publish'
// detail omitted
end
#article.save
end
But I think using the view related string is not good. Could you tell me another way to accomplish this?
UPDATE: Since these buttons are in the same form, they're all going to the 'create' action, and that's OK for me. What I want is to handle that within the create action, such as give the Article model a 'status' column and holds 'public' or 'draft'.
This was covered in Railscast episode 38. Using the params hash to detect which button was clicked is the correct approach:
View:
<%= submit_tag 'Create' %>
<%= submit_tag 'Create and Add Another', name: 'create_and_add' %>
Controller:
if params[:create_and_add]
# Redirect to new form, for example.
else
# Redirect to show the newly created record, for example.
end
it can also be done on the form_for helper like this
<%= f.submit "Publish",name: "publish", class: "tiny button radius success" %>
<%= f.submit 'Mark as Draft', name: "draft", class: "tiny button radius " %>
and the logic is the same on the controller
if params[:publish]
// your code
elsif params[:draft]
// your code
end
We solved using advanced constraints in rails.
The idea is to have the same path (and hence the same named route & action) but with constraints routing to different actions.
resources :plan do
post :save, constraints: CommitParamRouting.new("Propose"), action: :propose
post :save, constraints: CommitParamRouting.new("Finalize"), action: :finalize
end
CommitParamRouting is a simple class that has a method matches? which returns true if the commit param matches the given instance attr. value.
This available as a gem commit_param_matching.
I remember coming across this problem once. You cannot keep two buttons and then call some action based on the params[:commit]. the submit button onclick is going to call the url the form refers to. There are certain bad ways to get the desired behavior. Keep a button to call the action the form refers to and to get another button to call a action, I used a link_to and then changed the styles to match a button. Also, alternatively you can use jQuery to change the url the form would call, hence deciding what action is invoked at run-time. Hope this helps.
You could also set some data attributes on the submit buttons and use JavaScript to change out the form action on click of one of the buttons
usually i using the suggestion given by John Topley (see answer above).
another way is using JQuery /JS changing the form action attribute- upon clicking the submit button
example:
form_tag({} ,:method => 'post', :id => 'reports_action') do
.......
.......
submit_tag 'submit', :onclick => "return changeAction();"
end
and then .....
function changeAction(){
$('#reports_action').attr('action','my_new_action');
}