I am trying to challenge my learning by creating a simple project using rails forms, however I have gotten really stuck and can't find any information that seems to help online.
What I am trying to do:
I want to create a rails application with no model (so no persistence of data). Simply put I want a user to enter a Soundcloud URL which then gets transferred to the controller where I can do more logic. Essentially I am really trying to understand the connection between the Rails form and the controller in rails. I have spent all day reading about HTML forms, as well as googling this exact question without really fully getting it.
I understand there are different form helpers, but what I cannot seem to understand is how to use these without a model. My biggest hang up right now is I cannot get the form values transferred to the controller. I thought I understood RESTful routes, PUT/GET etc.. but this has made me super frustrated that I cannot seem to get my head around this. Any advice is super appreciated.
The code:
Below is the specific code I am struggling with, currently when I get submit the form it crashes giving me an error based on routes, and that's where I am stuck.
Problem code is found in the _form.html.erb file in views:
<%= form_tag '/show' do %>
<%= label_tag(:soundcloud_url, "Please enter a valid Soundcloud Artist URL:") %>
<%= text_field_tag(:soundcloud_url) %>
<%= submit_tag("Let's go!") %>
<% end %>
Routes.rb:
Rails.application.routes.draw do
resources :soundcloud_query
root 'soundcloud_query#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Error:
Routing Error
No route matches [POST] "/show"
rake routes output:
Prefix Verb URI Pattern Controller#Action
soundcloud_query_index GET /soundcloud_query(.:format) soundcloud_query#index
POST /soundcloud_query(.:format) soundcloud_query#create
new_soundcloud_query GET /soundcloud_query/new(.:format) soundcloud_query#new
edit_soundcloud_query GET /soundcloud_query/:id/edit(.:format) soundcloud_query#edit
soundcloud_query GET /soundcloud_query/:id(.:format) soundcloud_query#show
PATCH /soundcloud_query/:id(.:format) soundcloud_query#update
PUT /soundcloud_query/:id(.:format) soundcloud_query#update
DELETE /soundcloud_query/:id(.:format) soundcloud_query#destroy
root GET / soundcloud_query#index
Github link:
https://github.com/gaelant/simple_soundcloud_app/commit/09c4c4df524bb721a0f472b4378cd8c1ff18177f
Note: I understand this is a basic question but I have just gotten really confused with this. I know the way the code above is written is not correct, but I have tried so many different things and I just don't understand what is going on, or if this is even possible without a model.
Your /show form action points to nowhere. rails routes shows you which url is valid and to which controller#action each of them lead.
The correct form action would be
<%= form_tag '/soundcloud_query' do %>
<%= label_tag(:soundcloud_url, "Please enter a valid Soundcloud Artist URL:") %>
<%= text_field_tag(:soundcloud_url) %>
<%= submit_tag("Let's go!") %>
<% end %>
A far more better way is to use the url_helpers available through your defined ressource in Routes.rb
Doing so your form action would look like this
<%= form_tag soundcloud_query_index_path do %>
<%= label_tag(:soundcloud_url, "Please enter a valid Soundcloud Artist URL:") %>
<%= text_field_tag(:soundcloud_url) %>
<%= submit_tag("Let's go!") %>
<% end %>
The hardcoded and the url_helper based solution will route your request to a controller class named SoundcloudQuery where the action create will be called. Inside this action you have to put all needed logic.
It's also possible do define that /show should point to a specifc controller and action. This would look like this.
post '/show', to: 'mycontroller#myaction', as: 'mypathnameforhelper'
A much more better explanation with many examples about routes and how to use them can be found in this quite good guide.
Rails Routing from the Outside In
Hint: You should stay close to the ROR naming conventions. Controllers should have pluralized names. So you should define your routes like this:
Rails.application.routes.draw do
ressources :soundcloud_queries
root 'soundcloud_queries#index'
end
and then rename your controller class and file accordingly into SoundcloudQueries and soundcloud_queries.rb. But this is not mandatory.
I think in this case you should not use the url in the form_tag but the controller action.
Let's say you've got a view called my_form.html.erb and a submit_form method in your soundcloud_controller.rb
def submit_form
params[:soundcloud_url] your logic
end
I'd set up the routes like this:
get 'soundcloud_query' => 'soundcloud_query#my_form' // didn't want to use 'resources' but it doesn't matter
post 'soundcloud_query' => 'soundcloud_query#submit_form'
The form would then look like:
<%= form_for :this_doesnt_matter, action: :submit_form do |f| %>
<%= f.text_field :soundcloud_url %>
<%= f.submit %>
<% end %>
I have the following code in my routes.rb file .
resources :users do
member do
get :following,:followers
end
collection do
put :activate_email
end
end
And I have a user email activation link like this :
<%= link_to "Activate",activate_email_users_url(email_token: #user.email_token),method: :put %>
When I click on the activate link , this is the url that is generated
http://localhost:3000/users/activate_email?email_token=WWNvMN-r_lXgovrQiDlSSQ
Update: Ok, So I think I kno what the problem is . When I look at the html source of the activation email in my gmail which contains the link_to , there is no data-method='put'. So that seems to be the problem . It is always sending a default GET request instead of PUT.
This is my user_mailer/registration_confirmation.html.erb file
<%= javascript_include_tag "application" %>
</head>
Please click on the following link to activate your email
<%= link_to "Activate",activate_email_users_url(email_token: #user.email_token), method: :put %>
This gives the following error :
undefined method `protect_against_forgery?' for #
So , the code <%= javascript_include_tag "application" %> is causing this error. Is there any way around this ?
Sorry, I do not know your purpose, but apparently you have a purpose to activate user.
Try this, if this solution not work, please tell me your action (activate_email) on controller!
see on rake routes output :
activate_email_users PUT /users/activate_email(.:format) users#activate_email
user GET /users/:id(.:format) users#show
when your generate
http://localhost:3000/users/activate_email?email_token=WWNvMN-r_lXgovrQiDlSSQ
Your problem was activate_email considered to be :id
users/activate_email => users/:id
And solution for your problem :
Try removing the method from the link. Its better specifying the method in your routes file. How about replacing match by put in routes as :
resources :users do
member do
get :following,:followers
end
end
put "/users/activate_email/:email_token" => "users#activate_email", :as => "activate"
and on view
<%= link_to "Activate", activate_path(:email_token => #user.email_token) %>
I have not tested this, but I guess this will suffice.
UPDATE
for Question : undefined method `protect_against_forgery?'
Add this to a helper that only your mailer template uses:
def protect_against_forgery?
false
end
NOTE : If You have new question, please create new "Ask Question" and aprrove answer is usefull for this question
If you're trying to activate a single user account you probably don't want to be specifying your route on the collection (which you would use for actions that operate on multiple users).
Here's some (untested) code that should point you in the right direction:
controller :users do
put '/activate/:email_token', :to => :activate, :as => 'activate_email'
end
Which should route a PUT to /activate/xxxx to the UsersController#activate action with a params[:email_token] set as xxxx. It should also give you a #activate_email_url route which you can pass the activation token (you can check what routes your app provides by running rake routes on the command line).
Google redirected me to this question even-though mine was related to rendering a template into a string and not just in the browser. My solution for the template problem was something along these lines:
action_controller = ActionController::Base.new()
action_controller.class_eval do
def protect_against_forgery?
false
end
end
file_string = action_controller.render_to_string('/some_template/template_file',locals: { local_variable: 1 }
all, I'm trying to get a custom action to work with a put method: in the
in _post.html.erb i have a link_to statement:
<%= link_to 'End now', post, :method => :put, :action => endnow %>
routes.rb contains:
resources :posts do
member do
put :endnow
end
and posts_controller.rb looks like:
class PostsController < ApplicationController
helper_method :endnow
[.. code for create, edit, destroy, etc ..]
def endnow
puts params
end
end
rake routes's relevant line looks like:
endnow_post PUT /posts/:id/endnow(.:format) posts#endnow
However, the action endnow helper doesn't run when clicking on this link.
Strangely, it does run with an index action (which i can tell from the puts command.
Of course, eventually the code for endnow will update #post, but for now, it just doesn't run properly.
Maybe i'm going about this the wrong way - all I'm trying to achieve is to update #post upon clicking the link to that post, and before showing it.
Any ideas / Alternatives?
Why not use the route helper method provided to you? Change your link to
<%= link_to 'End now', endnow_post_path(#post), method: :put %>
Things you're doing wrong:
If you want to specify the :action, use the Symbol for the action (you're missing a colon). :action => endnow should be action: :endnow
I will assume you have a #post instance variable you're passing from your controller to your action. You should be using that instead of post (unless you do in fact have a local post variable you're omitting from your code)
You are using endnow as an action; you should remove the helper_method :endnow line in your controller because it's not something you want to/should be accessing from your view.
This can all be avoided by using the route helper (for endnow_post you'd append _path to get the local route path: endnow_post_path), and pass in your #post as an argument.
Because you're trying to do a PUT request, you must make sure you have something like jquery-ujs included in your asset pipeline to convert these links to form submissions behind the scenes; browsers don't support PUT via the click of a link on their own.
As for why you're getting the template error when you get your link_to working, Rails is telling you that you need to create a app/views/posts/endnow.html.erb file. Your action has only puts params which does not terminate execution, leaving Rails to assume you still are trying to render some endnow.html.erb template.
Are there other ways to do what you're trying to do (change a single attribute of a specific model)? Sure. Are there better ways? That's pretty subjective; it may not be the most RESTful way, but it's arguably easier to deal with (if for example there are very specific authorization rules to check before updating the attribute you are modifying in endnow. Does the way you've started fleshing out work? Absolutely.
Finally, as a bump in the right direction, after you fix your link_to and remove the the helper_method as I have described above, your endnow action might look like this:
def endnow
post = Post.find!(params[:id])
post.some_attribute_here = some_new_value_here
post.save
redirect_to :root and return # <- this line sets a redirect back to your homepage and terminates execution, telling rails to do the redirect and **not** to render some endnow.html.erb file
end
I know this is a trivial question. But I have search all over google but cannot find a simple answer to this question.
Basically I have a line that says <%= link_to 'Run it', :method => 'doIt' %> in the view, then in the corresponding controller, I have the doIt method as follows:
def doIt
puts "Just do it"
end
I just want to check that if i click on Run it, it will output the string "Just do it". I ran this on localhost and there is no errors, but I can't find the output "Just do it" anywhere. It is not displayed in the rails console or rails server log. I just want to know where does puts output the string to , where to find it ?
Round 2: So this is what I tried ....
Added this line in the index.html.erb (which is the root)
<%= link_to 'Run it', :method => 'do_it' %>
and in the url, it is just basically http://localhost:3000/ (since i route controller#index as root)
The display is just an underlined 'Run it' that links to 'do_it' method in the controller.
In the controller, i include this method
def do_it
logger.debug "Just do it"
end
when i click on 'Run it', the url change to http://localhost:3000/gollum_starters?method=do_it and in the development.log, the following is written into it:
Started GET "/gollum_starters?method=do_it" for 127.0.0.1 at 2011-08-25 15:27:49 -0700
Processing by GollumStartersController#index as HTML
Parameters: {"method"=>"do_it"}
[1m[35mGollumStarter Load (0.3ms)[0m SELECT "gollum_starters".* FROM "gollum_starters"
Rendered gollum_starters/index.html.erb within layouts/application (3.6ms)
Completed 200 OK in 16ms (Views: 7.7ms | ActiveRecord: 0.3ms)
Additionally, i tried all the logger.error/info/fatal/etc ... and Rails.logger.error/info/fatal/etc, all did not print out the line "Just do it" in the development log
#Paul: I did not touch the environment folder or file, i assume by default when a new rails app is created, it is in development ?
#Maz: Yes you are right, I am just trying to test if the do_it method is getting called. To do that, I just want to print something out in the controller. Can't think of any way simpler that just print a string out, but this problem is making me miserable. I am just using textmate, no IDE.
Round 3:
#Paul thx alot, but i encountered error
My routes files is now:
resources :gollum_starters
root :to => "gollum_starters#index"
match 'gollum_starters/do_it' => 'gollum_starters#do_it', :as => 'do_it'
My index.html.erb is now:
<%= link_to "Do it", do_it_path %>
My gollum_starters_controller.rb
def do_it
logger.debug 'Just do it'
end
I am getting this error:
Couldn't find GollumStarter with ID=do_it
the error is in here, 2nd line:
def show
#gollum_starter = GollumStarter.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #gollum_starter }
end
end
I wonder why does it route to show ? When i click do_it, it actually goes to localhost:3000/gollum_starters/do_it which is correct, but apparently the error points to the show method ?
Round 4:
#Paul, i shifted resources :gollum_starters down:
root :to => "gollum_starters#index"
match 'gollum_starters/do_it' => 'gollum_starters#do_it', :as => 'do_it'
resources :gollum_starters
but got this error (omg i wanna kill myself),
Template is missing
Missing template gollum_starters/do_it with {:handlers=>[:erb, :rjs,
:builder, :rhtml, :rxml], :formats=>[:html], :locale=>[:en, :en]} in
view paths "~/project_name/app/views"
:/
---------- Answer to Round 4 ------------
Basically as the error explains, there is no template(i.e a webpage) to show hence error thrown. The solution is to add a redirect_to , in this case I redirect to root_url.
def do_it
logger.debug 'Just do it'
redirect_to(root_url)
end
Everything works now, "Just do it" finally outputs to development.log and the rails server console.
Thank you Maz and Paul and Andrew for helping me out. Learn a lot.
That link_to does not do what you think it does the value for :method is referring to the HTTP verbs.
Taken from the docs for ActionView::Helpers::UrlHelper
:method - Symbol of HTTP verb. Supported verbs are :post, :get, :delete and :put. By default it will be :post.
You would need to define a route in your routes.rb file that uses your method
# The order of routes is important as the first matched will be used
# therefore the match needs to be above 'resources :controller'
match 'controller/do_it' => 'controller#do_it', :as => 'do_it'
resources :gollum_starters # <--- This needs to be below the match or this will catch first
The controller/do_it is the route to be matched
The controller#do_it is the controller followed by the action to be used (separated by #)
The value for :as creates the path do_it_path that can be used in your link_to
Your link_to may look something like
<%= link_to "Do it", do_it_path %>
And to complete the lifecycle of a request you will need to add a view to be rendered
app/views/gollum_startes/do_it.html.erb # <-- Add file
Summary
Doing all of this creates a bit of a mess just to print something out to the logs, but it should help you understand the whole lifecycle a bit better now. Plus this answers serves as a document to help you rewind this mess.
You are not understanding what "method" means in the context of a link.
The "method" here refers to the request method, which means the kind of request you are asking the browser to make. From the perspective of a RESTful application like Rails there are four relevant request types: GET, POST, PUT, and DELETE. These request types affect how the controller responds to a request.
GET => INDEX or SHOW
POST => CREATE
PUT => UPDATE
DELETE => DESTROY
There are two other "standard" rails actions, NEW and EDIT. These are GET requests to present an interface to the user. NEW gives you a form to POST (CREATE) a new object, and EDIT gives you a form to PUT (UPDATE) and existing one.
See the rails guide for more on how HTTP Verbs relate to CRUD operations.
The important, basic thing to understand is that links, by default, are GET requests, and forms, by default, are POST requests.
So, when your link looks like this:
<%= link_to 'Run it', :method => 'do_it' %>
...it is bogus. There is no such HTTP method as "do_it", so you're not triggering anything. Because there is no such method, Rails actually passes this on as a parameter of the URL. Hence if you click that you should see your url bar now says ?method=do_it at the end.
There are several problems with what you're trying to do. First of all, the link_to helper expects at least two arguments: 1, the text for the link, and 2 the HREF for the link. So, you really need to use:
link_to 'Run it', url
Second, you need to know what URL to pass to get your controller action.
Please be familiar with the following rails convention: When referring to a controller action you can abbreviate it using the form: controller_name#controller_action. e.g. pages#show or articles#index.
Assuming your controller is called ExamplesController, you can manually trigger the seven standard controller actions as follows:
link_to 'examples#index', '/examples'
link_to 'examples#show', '/examples/123' # 123 is the id of a specific example
link_to 'examples#new', '/examples/new'
link_to 'examples#create', '/examples', :method => :post
link_to 'examples#edit', '/examples/123/edit'
link_to 'examples#update', '/examples/123', :method => :put
link_to 'examples#destroy', '/examples/123', :method => :delete
Note that in the above, INDEX, SHOW, NEW, and EDIT are all GET requests. You could specify :method => :get but that is unnecessary
To abstract this away and take care of assigning the ID when required Rails provides path helpers.
So, to repeat the above using the path helpers you could use:
link_to 'examples#index', examples_path
link_to 'examples#show', example_path( #example )
link_to 'examples#new', new_example_path
link_to 'examples#create', examples_path, :method => :post
link_to 'examples#edit', edit_example_path( #example )
link_to 'examples#update', example_path( #example ), :method => :put
link_to 'examples#destroy', example_path( #example ), :method => :delete
Now, you get these path helpers from the router, and they are defined in your routes.rb file. Within that file if you define:
resources :examples
...then you will get all the path_helpers above.
If you are using a normal RESTful controller and you want to add a custom action, then you need to make one decision: does the action operate on the entire set of objects handled by that controller (like index) or just a single specific one (like show). The reason this is important is this tells the router whether the new action you're defining needs to receive a record ID as part of the request.
If you want to act on the entire collection of objects, you define:
resources :examples do
collection do
get 'do_it'
end
end
If you want to act on just a single member of the collection you define:
resources :examples do
member do
get 'do_it'
end
end
Where I wrote 'get' in the examples above you can use any of the four verbs -- GET is normally what you do if you want to show a page, and POST is normally what I'd use if you're submitting a form. You can also write this shorthand like so:
resources :examples do
get 'do_it', :on => :collection
post 'something', :on => :member
end
For more on defining custom controller actions, see the rails guide.
Now that you've defined a route, you should run rake routes in terminal to see the name of the new path helper. Let's assume you added do_it as a collection method, your path helper would be: do_it_examples_path.
Now then, back to your link, if you put:
<%= link_to 'Do it.', do_it_examples_path %>
... then you would trigger the do_it action. When the action is triggered your puts should normally render to the server log (assuming you're running rails s in a terminal window you should see it right after started GET on examples#do_it...).
Now, in the browser you would get a missing template error as a GET request is going to expect to render a view, but that's a subject for another question. Basically, now you should understand what the controller actions are, how you get to them. If you want to learn more about what to do with your controller action, see the guide :)
I hope you understand what's going on now. Feel free to ask questions.
You want to use the Rails logging mechanism:
http://guides.rubyonrails.org/debugging_rails_applications.html#sending-messages
This means that even if you don't launch the server using rails s the output will still go to the right place.
After playing around with links in Rails for a view hours i've managed to actually get a link to invoke a method in my controller. But i still don't understand why all my other attempts failed. Im hoping you could help me out with that.
I have the scaffold "Cars". When in the show view for a car, id like to click a link that invokes the method "drive" in my Car controller.
This WORKS: <%= link_to "Drive", drive_car_path(#car) %>
It seems this only works if i have this is my routes.rb:
resources :cars do
member do
get 'drive'
end
end
Why does <%= link_to "Drive", car_path, :method => :drive %> not work?
Do I need to put a GET in the routes.rb file for every method I create in my controller?
I can't seem to find any sites explain how to use links together with routes. They only seem to come separate. Do you guys have any easily understandable tutorials on this?
Try link_to "Drive", :controller => "car", :action => "drive"
Also, method is for choosing the HTTP method (GET, POST, ...). It's not method as in routine.
Be sure to check out Rails Routing from the Outside In and The Lowdown on Routes in Rails 3, they're both awesome resources.