How do controller actions know which REST operation to perform? - ruby-on-rails

When I'm at /profile/new, for example, and I submit a form to create a profile, Rails knows to perform a POST operation; and when I update that profile from /profile/edit/1, Rails knows to perform a PUT operation... My question is, how does it know to do that?
I can't understand how this works past the controller. What exactly is going on in the background? I've dug around a little bit and I know ActiveRecord and ActiveResource? are involved, but I'd like to know the details. I've only been around since Rails 2.2 and every resource I find seems to teach by example. I'm interested in understanding how things work at a lower level, but there's nothing to guide me through learning by reading apis & source code.

You know how a view page of new page or edit page in a user's scaffold looks right?
form_for(#user) # something like that
So this is a helper method which you can find inside action_view/helper .. file
Basically the form rendering for new and edit will be decided by this form_for method, what this form_for method will do is (I just made some bullet points)
1) It will check what type of input you gave in your form_for
(check api for different ways of using form_for helper)
2) It will decide the the html options based on the below code
if object.respond_to?(:new_record?) && object.new_record?
{ :class => dom_class(object, :new), :id => dom_id(object), :method => :post } # for new
else
{ :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put } # for edit
end
3) It will do one more thing for edit page it will add a hidden field which will have user's id value in it.
Please let me know if you need some more details. I will update my answer accordingly.

The best I can tell, it relies completely on what action you're performing in the controller. For instance, #object.new would be a POST action, where-as #object.find([:params]) would be a GET action, based upon RESTful practice.
I could be off-base, as I'm in the same boat as you but that's my interpretation of it.

Related

link_to 'new' action in a different controller?

I want to have a link at the bottom of my show.html.erb that links to the new action in a different controller.
class Sample < ActiveRecord::Base
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :samples
end
So, at the bottom of the show action for songs, I want to link to new for samples. This seems pretty easy, but I'm struggling to figure this out. I would also like to pass the id from the song to the form as :song_id
Fiddy, because you're new, let me explain how this works...
Routes
Your problem is that you don't understand the Rails routing structure - I'll hopefully explain it for you.
Rails, since it's an MVC framework, builds a series of "routes" for you. These "routes" are stored in the file available at config/routes.rb.
Routes, as described by the Rails documentation are as follows:
The Rails router recognizes URLs and dispatches them to a controller's
action. It can also generate paths and URLs, avoiding the need to
hardcode strings in your views.
The most important thing you should consider here is the way the routes generate paths for you. These paths are simply Rails "helper" methods, which you can call from your views. The reason these exist is two-fold -
They provide you with a DRY (don't repeat yourself) way of accessing / manipulating data
They are constructed around objects, helping maintain the object-orientated nature of Rails
These will likely mean nothing to you. However, what you need to realize that if set up your routes correctly, it seriously helps your app's infrastructure immensely.
--
Rails
This leads us quite nicely onto appreciating the way in which Rails works
Rails is an MVC (model view controller) framework. This might seem somewhat trivial, but in reality, it's one of the most important aspects to learn about Rails development, and here's why:
The Rails software system works by taking "requests" (user input) and then routing them to specific controller#actions. Those controllers then build model data from the database, and will translate that into either variables or objects, which you can use in your view.
The reason I mention this is that this type of development takes a lot of getting used-to, in that your program's flow is not about logic / functionality, but the accessibility of data. Therefore, when you ask about the routes or other parts of your app, you need to firstly remember what data you wish to show, and also how you want that data to be shown - this will give you the ability to construct & use the routes / controller actions which will get it to work properly
--
Fix
In terms of what you're saying, the way you'd go about achieving the result you want will be to use a nested route:
#config/routes.rb
resources :songs do
resources :samples #-> domain.com/songs/:song_id/samples/new
end
This will create a new route for you (which you can check by firing rake routes in your rails c (console). This will give you a path to use for your samples#new action:
#app/views/songs/show.html.erb
<%= link_to #song.name, new_song_sample_path(#song) %>
The above link will take you to the samples#show action, which you'll be able to populate with as much data as you require from the samples controller. The important thing to note is this action will have params[:song_id] available for you to either build an object from, or otherwise
<%= link_to "New Sample", new_sample_path(:song_id => #song_id) %>
Where #song_id is the variable that has that id in it.
Set paths in link_to tag which you can get by running rake_routes in terminal.
Ex
link_to "New song", new_sample_path(#song)
In the example given above #song is the instance variable of your current page.
You can also get some idea from here:
link_to Base URL Randomly Changed
Song Model:
accepts_nested_attributes_for :sample, allow_destroy: true
Route:
resources :songs do
resources :samples
end
Song's Show file:
<%= link_to "New Sample", new_song_sample_path(#song) %>
in url it will be:
/songs/:song_id/sample/new
Try this and let me know it works or not... I hope this helps you

Rails adding method, action to controller?

Rails... pretty hard to learn from 0
Im making project about autosop.
1 user makes advertisement, sets, where he is going, and other users can join him.
I have problem adding action, since i just dont know how to make it.
I have made already authorization, autentification.
Where things get tricky - i have 2 scaffolds - Advertisement (user fills form with data). And AdvertisementCommitment(dont even need scaffold imho, but.. ok). In Database - it is user_id and advertisement_id.
At the moment, User can make advertisement.
If i go to /advertisements index page, i get all advertisements listed nicely. I want to add a link to each advertisement - to commit.
Question - where and what i need to do, to make this action work ? How to pass that advertisement to advertisement_commitment ?
SOLVED
<%= link_to "Join", { :controller => 'advertisement_commitments',
:user_id => current_user.id, :advertisement_id => advertisement.id},
:method => :post %>
maybe it will be useful for someone :)
have a look at http://ruby.railstutorial.org/
You will then have a better idea of how Rails works, and how you can get Rails to do what you want...

Form submission and hyperlinks using GET and POST

I have a search resource, the user can perform searches by filling out a form and submitting it, the create action is called, the Search is saved, the show action is called, and the results are displayed. This all happens with the default POST, and all works fine.
The user may want to save his search in the saved_search table (i don't use the Search table for this purpose as this table stores all searches for the purpose of compiling statistics, and gets cleared on a regular basis). Once the Search is saved, it can be re-run by clicking a hyperlink, this is where i start to get problems.
I see no way of getting my hyperlink to run the create action of Search, with a POST request, and the necessary data.
I then decided to try to get both form submission and the hyperlink to perform a search using a GET request, i was unable to get form_for to run my Search create action using a GET request, it always seems to get routed to my index action.
Can someone suggest a good restful solution to this problem please.
Many thanks
I'm not quite sure what you're trying to do here. If you want to have a form submit with a GET request, you can override the HTML attribute on the form_for helper:
<% form_for blarg, :html => { :method => 'get' } %>
blabla
<% end %>
Rails also supports a way of "faking" the HTTP method by using a "magic" parameter (called "_method"), which makes Rails behave as if it had seen the HTTP method in that parameter.
If you send the form as "get", you must make sure that none such parameter is set. If you wanted to let a hyperlink send a "POST", tweaking this would be the way (a browser will not send a real POST on a click on a link)
Jon,
If I understood right, if the search is already saved, you could just make a get on the resource of the saved search like you did the first time and use the show action to display the result.
Anyway, if you still wants to do a post with a link, the helper method link_to does it for you. Check it out:
http://www.51773.com/tools/api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#M001597
With a :method => :post option it will create a hidden form and post your data.
Hope it helps.

One Controller Action or Two for Ruby on Rails pages

I currently have one model, one controller with one action to list all the items in the model.
What I need to do is display different data from the model in two separate views. Is there a way I can use one controller action to display different views based on params, or should I create another action?
The reason why I hesitate to create another action is because I'll have to essentially duplicate all the routing I setup for the previous action.
Thanks for any ideas.
I'm not entirely sure that you've provided enough information to give what could be considered a 'good' answer, but if I'm understanding you correctly, this should be possible.
For example, couldn't you do something like this?
def show
#my_objects = MyObject.all
if params[:full_view]
render :action => 'show_full_fiew' and return
end
# if you get here, it will render the 'show' action
end
Let me know if that helps. If you could give some more information, I might be able to clean up this example to be a bit more informative.
You don't mention if you're using resource routes or not. If so, I'd just add a new option to your routes.
map.resources :products, :collection => { :some_great_name => :get }
You really shouldn't worry about adding views or new actions to your controller. An action should usually only have a few lines of code. If your controller actions start to grow in complexity you should think about moving that logic into your model.

on Rails, how does form_for and validate_presence_of work hand in hand?

when we have validate_presence_of :name in the model and then when we put in the create action that we re-render 'new', then the form_for will populate the fields, and error_messages_for 'story' will have the correct error message.
this is really great, and and the same time, this looks like magic... i found that many books don't explain how the magic occur. is it by some global variable?
when the form_for is called... is it using the #story that came back from the #story.save, instead of the #story = Story.new from the new action? so if i use :story for the form_for, the fields won't be populated on error?
sometimes i feel that i am playing magic when using Ruby on Rails, except I don't know how the magic happens... kind of like if I make the rabbit appear, but I don't know how I did it. So I really want to know the inner workings of Rails.
Yes, Rails is very magical. Unfortunately these are just things that you have to learn to live with, and once you get used to the conventions you get to use the magic to do some very complicated things with great ease.
There are three separate issues here that are relatively simple individually but look very magical when you take it all in at once. Let's break them down one by one:
When validations fail, they disallow the model object from being saved and add errors to the object.errors hash.
When you run #story.save, it kicks off all the validations. Since #story.name is blank, validates_presence_of :name adds an error to the object.
Instance variables in the controller are available to the views they render.
So, yes, it is the same #story that the view has access to - the one that is invalid and has error information attached to it.
form_for takes many forms, and the one you're using is very smart
The form_for tag in your view probably looks like this:
<%= form_for #story do |story| =>
This is a special version of form_for that infers all kinds of information from the object passed in and renders the form appropriately. #story has some of its fields populated because of the line
#story = params[:story]
in your controller, so it goes ahead and fills in those fields for you. It does some other things, too - for example, it checks #story.new_record? to see if it should use the POST HTTP method (RESTful create) or the PUT method (RESTful update).
In summary, there are lots of little bits of magic to learn, but once you do the big magic is much easier to understand. Good luck!

Resources