I'm new to rails and still learning the ropes via railstutorial, but the book does all changes to the db via form submissions (http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html). But how would I go about submitting (updating the db) without this, lets say I want to add some score which is computed on page ,(for example via a js counter - for simplicity lets just say its a constant 10) and my db consists of a column called score. Then after pressing a submit button how would I go about updating the db?
Thanks
Two trivial ways:
Use a form
Use a URL parameter with a link
The processing (other than GET v. POST) on the Rails side is identical--it's just a parameter.
If you're using JavaScript, there's not necessarily a reason to not use a form, though, since you could just update a form input element and submit normally.
It is quite simple, actually.
The constant 10 is submitted from the view. The submit needs to point to the controller action that will handle this request. So the submit button should build the url using :controller, :action, :id parameters. In a form, this is handled in the form_for declaration. You will deal with in the button_tag declaration.
The routes should be configured so that this message can reach the controller/ action.
The constant 10 is transported in the params hash. If the field is my_counter, then look for params[:my_counter]. If the form had been for a model, say tweets, then it might be in params[:tweet][:my_counter].
In the controller action, possibly update, you will first fetch the record to change with something like #score = Score.find(:params[:id]). This params[:id] is also coming from the view with the submit. Change the counter here, and save.
def update
#score = Score.find(:params[:id])
#score.counter = params[:my_counter]
#score.save
redirect_to :action => :index # or wherever
end
Good luck.
Related
I've generated a simple rails app using scaffolding. In the user model I have added a validation for the presence of name. I am using debug(#user.object_id) and showing at the top of my user edit page in edit.html.erb
I've purposefully left the name blank and tried to submit the form. The usual errors are rendered but every time I submit the form, the #user's object id changes. Could someone please explain why the object_id is changing? My assumption was that the #user is still the same object (since we're in the same page, just adding errors and re-rendering the edit.html.erb on failed update)
You probably confused by the fact, that render :edit in the update method just renders the edit template, but doesn't redirect to the edit page - that's right.
But, actually this is what happens in your scenario:
edit page is visited, #user assigned in the edit method of UsersController
form is submitted, update action is called and found #user is assigned in the update method and renders edit template
Thus, on submitting a form different method is called and the state changes
No, your assumption is not correct. HTTP calls are stateless, meaning that state does not persist between calls (i.e. every call is independent of each other). Every time your form is submitted, a new object is created and assigned to the variable #user. Since a new (and different) object is created during each call, their object_ids will be different.
I have a standard html form post that is being persisted and validated with a rails model on the server side.
For the sake of discussion, lets call this a "car" model.
When
car.save
is invoked the validators fire and set a list of fields in error within the model.
How best can I get an object back that has the fields whose validation failed removed, i.e. such that I don't have to delete the fields on the form the user entered correctly?
The validation patterns they use are great but how do I get a hook to when the validation fails such that I can delete the bogus entries from the model?
I could see manually iterating over the errors list and writing a big case statement but that seems like a hack.
In your controller, render the new action from your create action if validation fails, with an instance variable, #car populated from the user input (i.e., the params hash). Then, in your view, add a logic check (either an if block around the form or a ternary on the helpers, your choice) that automatically sets the value of the form fields to the params values passed in to #car if car exists. That way, the form will be blank on first visit and in theory only be populated on re-render in the case of error. In any case, they will not be populated unless #car is set.
EDIT:
Don't do this (see comments) but if you must here is how:
I ended up using a call to
#car.invalid?
inside of a loop to remove the invalid entries:
params[:car].each do | key, array|
if #car.errors.invalid?(key)
#car[key.to_sym] = ''
end
end
render :action=> 'new'
Note the use of the render here vs. redirect. Getting error messages to persist from the rails model validation across a redirect appears tricky, see:
Rails validation over redirect
I still feel like there should be a helper method for this or a better way.
I have a form with a list of stuff, and an action already in place to update items.
I want to have another button, which when clicked triggers a different action to remove the selected items.
= form_for #new_item,:url => {:controller => "item_lists",:action => "update_list" } do |f|
- #items.each do |it|
%input{:type=>"hidden",:name=>"item_list[#{it.id}]position",:value=>it.position, :class=>'position'}
%textarea{:name=>"item_list[#{it.id}]field1"}
=it.field1
%textarea{:name=>"item_list[#{it.id}]field2"}
=it.field2
%input{:type=>'checkbox', :name=>'selected_items[]', :value=>it.id}
=(it.valid?) ? "" : it.errors.full_messages
%input{:type=>"submit", :value=>"Save changes", :name=>'save'}
%input{:type=>"submit", :value=>"Remove selected", :name=>'delete'}
This question seems to indicate I should inspect params in my action to figure out what was clicked. But that feels messy, my controller could quickly degenerate into a mass of ifs when I add more actions.
Is there a more elegant way to do this, i.e. get it to just route to the correct method?
Thanks for any help...
This doesn't really gel with REST. In REST and Rails you're typically going to have one action per endpoint, not decide on the endpoint based on some criteria in the request.
That being said, you can filter actions based on the submit button by checking the name of the button pressed. See this SO question.
I'd argue though that this is only appropriate if your form is doing slightly different things, like perhaps a submit button that updates in place versus a submit button that redirects somewhere afterward, e.g. "Update" versus "Update and Continue" (contrived, but you get what I mean).
Addressing your concern in the comments, your method wouldn't have to devolve into a long sequence of ifs. You could just write some code to determine which method to call based on the name of the submit button. A simple implementation might be:
# your form action
def update_list
send update_list_action
end
protected
def update_list_action
# just return the first action name found in the params
action = %w(save delete).detect {|action| params[action] }
"update_list_#{action}"
end
def update_list_save
# handle save
end
def update_list_delete
# handle delete
end
I would suggest you to add a dropdown menue with the option "delete", "update",... and add some jQuery code that observes the selected item and changes the action of your form depending on the value because you shouldnt use one action to update and delete objects! There should be one action for updating and one for deleting!
I am currently using a link_to helper in View to pass parameters like title , author ,image_url and isbn back to controller
<%= link_to 'Sell this item',new_item_path(:title => title, :author => authors, :image_url=>image, :image_url_s=>image_s, :isbn=>isbn, :isbn13=>isbn13 ) %>
Controller will then assign the parameters to an object to be used by a form in View later(in new.html.erb)
def new
#item = Item.new
#item.title = params[:title]
#item.author = params[:author]
#item.image_url = params[:image_url]
#item.image_url_s = params[:image_url_s]
#item.isbn = params[:isbn]
#item.isbn13 = params[:isbn13]
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #item }
end
end
new.html.erb will then be called.
This is all working fine but the url shows all the parameters
http://localhost:3000/items/new?author=Michael+Harvey&image_url=http://ecx.images-amazon.com/images/I/51vt1uVjvLL._SL160_.jpg&image_url_s=http://ecx.images-amazon.com/images/I/51vt1uVjvLL._SL75_.jpg&isbn13=9780307272508&isbn=0307272508&title=The+Third+Rail
Is there any way I can make the parameters not show up on the URL?
Maybe you could encode the parameters and decode them in the controller to deter users who may want to modify the url? Might be overkill but...
>> author=ActiveSupport::Base64.encode64("author=jim")
=> "YXV0aG9yPWppbQ==\n"
>> ActiveSupport::Base64.decode64(author)
=> "author=jim"
A POST can be used to move the parameters out of the URL and into the request, but this is not the "correct" or best practice. HTTP standards are such that non-GET requests are meant to be used only for requests that change state on the server. This is why you get a warning when you refresh a page that was generated in response to a POST.
There is nothing wrong with having parameters in the URL. So much focus should not be made on what appears to the URL bar, let alone what's after the ?. If however you have some need (i.e. insistence of a client) to remove them, you have several options, two of which John mentions.
I'm assuming your "new" action is REST-style, in that it's generating a form that would have to be submitted to change state on the server. Therefore your options might be:
Use POST, even though it's not standard compliant. Not recommended.
Use AJAX GET. This requires javascript, and ajax handling does add requirements such as the use of a JS framework and testing.
Use GET (or POST), but capture the parameters and store them, the redirect the user back to another clean URL that displays those stored value. You could store those in the session hash, or create a database record of them. Actually you really should use POST in this case, since you are effectively changing state on the server by storing those parameters. In this case, if the user refreshes the page he is directed to, those parameters will be preserved. This effectively removes the browser warning on refresh, something I can certainly appreciate.
There are two options that I can see and both involve JavaScript:
Have the link populate hidden form fields for the parameters and then submit the form using an HTTP POST request
Have the link submit an AJAX request to the controller action (using an HTTP GET unless clicking the link changes server-side state, in which case a POST should be used)
I think I would go with the second approach.
Why not write them to the session? It looks like you might have less than 4k in data there. Just remember to wipe it.
I have an action that doesn't require a form. So it really only needs the one 'edit' method instead of the RESTful 'edit' --> 'update'. Is there any reason not to do this or a better way?
def edit
#Do a POST(PUT)
end
The harm is that a user could easily navigate to that url and perform a potentially destructive action.
/noform/edit #URL typed by user => Action Performed
/noform/update #URL typed by user => Error is thrown, No Action Performed
A normal browsing experience generates GET requests to the server. The assumption is, any page you can easily navigate to (or type into your address bar) will not perform any data changing functions.
A POST request, generated via a form submission or a AJAX request expects the result that data is changed on the server.
Similarly the two rails "faked" versions of PUT and DELETE also are not actions you could simply navigate to using a browser.
The solution
The solution is to have only the update action and where you originally would have linked to edit use something like the following:
button_to "Add new tracker", noform_path, :method => :put
If there is any type of error, you may still need an edit path to show the user so they can correct something. But from what you have described, a single update action should do the trick.
Gets should always be idempotent -- that is they should not perform any action that will alter the state of the application, database, etc.
Just as an aside -- in true RESTful form an edit would be performed by an HTTP Update action, but Rails simulates this with a post and a hidden value on the form, since browsers don't have HTTP Updates.
It's still not clear to me why you need an update without an input field. Perhaps a little more detail would be helpful.