Implementing REST properly

Hi,

Same question is in Link_to parameters ignored - Rails - Ruby-Forum but since
it evolved from a different question I move it to its own topic.


I have some resources: Programs, Users and Comments.
Users make comments about programs.

I want to create a RESTful web service.

So I have generated 3 scaffolds and I can CRUD each of them separately.
In the models I have defined the belongs_to and has_many accordingly


From the Programs list I want to add a link next to each program in the
list so I can view what comments we have for that precise program.

I know I can use <%= link_to ‘View comments’,
:controller => ‘comments’, :action => ‘index’ %> and that will get me to
the general list of comments for all the programs.

But I want to see only the comments for a given program. So I guess I
can
add the program.program_name somehow in the link_to.

But do I need to add a new method to the comments controller? Or should
I modify the comments controller index/show method so that it would take
an argument and find the records accordingly? Would routes.rb take care
of that with some changes? What is the proper approach?

Thanks!

Hi again,

This is what I’ve done in the Programs index view:

<%= link_to ‘View comments’, :controller => ‘comments’, :action =>
‘index’, :program_name => program.program_name %>

And this in the comments controller index function:

GET /comments

GET /comments.xml

def index

if params[:program_name]
@comments = Comment.find(:all, :conditions => [“program_name = ?”,
params[:program_name]])
else
@comments = Comment.find(params[:id])
end

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @comments }
end

end

So do you thing that’s the right way?

Cheers

Hi,

My understanding of the RESTful way would lead me to using nested
resources in this situation

so your urls would be

/Programs/1/comments for index of all comments
for a program whose id is 1
/Programs/1/comments/new for new comments for a program
whose id is 1
/Programs/1/comments/10/show for showing comment 10 for a
program whose id is 1

and so on for all 7 crud actions. that way your comments controller
does not need logic like that.

so in your routes.rb you would do this

map.resources :programs do |program|
article.resources :comments
end

which will build all those routes for you you can check the routes
it creates by using

rake routes

from command line. it will list all of the routes that your
routes.rb file sets up. very useful for finding the correct
dynamic_url

for instance a few from my routes rb are

           company GET    /

companies/:id
{:action=>“show”, :controller=>“companies”}
company_profile GET /companies/:company_id/
profile/:id {:action=>“show”, :controller=>“profile”}
new_company GET /companies/
new
{:action=>“new”, :controller=>“companies”}

left hand column can be used by appending _url or _path accordingly

new_company_path in your index view for companies check your
scaffolded views and controllers and you’ll see them everywhere. the
table in rake routes shows the mappings

hope this helps :slight_smile:
Nathan

hope this helps :slight_smile:

Sure it does. Thanks!

I’ve been now playing with this again.
Sorry to be back with the same one.

Yeah the rake routes is great to see all options clearly.
However I’m facing a problem:

This is what I have now in the routes:

map.resources :comments

map.resources :programs do |program|
program.resources :comments
end

Everthing is more or less working. I have created some programs and then
using “/programs/:program_id/comments/new” I have created some comments
for programs.

But, according to routes using “program_comments GET
/programs/:program_id/comments” should list all the comments for that
program id.

However that always shows all the comments for all the programs. That is
getting into the controller via the ‘index’ method which is:

GET /comments

GET /comments.xml

def index
@comments = Comment.find(:all)

respond_to do |format|
  format.html # index.html.erb
  format.xml  { render :xml => @comments }
end

end

And that finds all the comments as the code is. So how can it find
comments only for a give program with no changes in that controller?

that way your comments controller does not need logic like that.

map.resources :programs do |program|
article.resources :comments
end

I have changed article with programs in the code above. Is my routing
still ok?

I must be doing something wrong again…

Thanks.

On 2/29/08, comopasta Gr [email protected] wrote:

However that always shows all the comments for all the programs. That is
getting into the controller via the ‘index’ method which is:

GET /comments

GET /comments.xml

def index

@comments = Comment.find(:all)

And that finds all the comments as the code is. So how can it find
comments only for a give program with no changes in that controller?

You need to change your controller so you scope the comments by the
program, such as:

before_filter :get_program

private
def get_program
@program = Program.find_by_id(params[:product_id]) || Program.new
end

public
def index
@comments = @program.comments
end

Note that the above will only work when called in a nested fashion,
such as /program/123/comments

If you try calling /comments, you’ll get an empty list

Mike

sorry i probably mislead you by showing that route in my last mail as
an example, i would get rid of the index action and show action
instead use the program view and controller to display the comments
in the context of the program, i can’t really envisage a situation
where you would want to show all your comments in an index view. but
i suppose you might want to have the index show a list of comments
for a given program.

basically you would find the program based on params[:program_id] and
use it to populate @comments like so.

program = Program.find(params[:program_id])
@comments = program.comments.find(:all)

given that other actions in the view will use program variable you
could use a before_filter to create @programs variable that can be
referenced by all the actions. so for instance your new action can
be done like this:-

def new
@program.comments.build
end

check out this tutorial, I confess i haven’t read it thoroughly but
just skimming it i think it will explain all you will want to know.

http://www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-
first-full-tutorial

cheers
nathan

sorry again just realised mike had replied already … :slight_smile: I don’t
think you would actually want the comments controller to respond to
just /comments so I would remove

map.resources :comments

from your routes.rb file.

cheers

just noticed that you have both a nested route and a non nested route
for comments.

so if you want both ‘/comments’ and ‘/programs/123/comments’ to work,
you’ll need something like the following:

comments_controller.rb

def index
@program = Program.find_by_id(params[:program_id]) if
params[:program_id]
@comments = @program ? @program.comments : Comment.find(:all)
end

Mike

Hi, reply to myself (and anyone interested of course…)

<% form_tag :action=>"destroy", :controller=>"session"  do %>
    <%= submit_tag 'Log out' %>
<% end %>

So my problem what that the controller was not found in admin. So the
fix is to use :controller=>"/session"

That will look in root controller and that’s where it is found.

Cheers!

Hi,

Rolling with Rails 2.0 - The First Full Tutorial - Part 1 | AkitaOnRails.com

That is a great tutorial. I’m using it as a base for my drafts.
As an addition I’m using restful_authentication plugin and everything
goes ok but one thing.

In all the public views I have login and logout buttons for the user:

<% form_tag :action=>"destroy", :controller=>"session"  do %>
    <%= submit_tag 'Log out' %>
<% end %>

And I can login and logout just fine.
My problem now is that if I try to use the same in the views that are
inside Admin I get the next error:

No route matches {:controller=>“admin/session”, :action=>“destroy”}

I have to admit that the routes thing still is very cryptic to me.

My current routes file is:

map.resources :users

map.resource :session, :controller => ‘session’

map.root :controller => ‘programs’
map.resources :programs, :has_many => :comments

map.namespace :admin do |admin|
admin.resources :programs
end

map.signup ‘/signup’, :controller => ‘users’, :action => ‘new’
map.login ‘/login’, :controller => ‘session’, :action => ‘new’
map.logout ‘/logout’, :controller => ‘session’, :action => ‘destroy’

I’ll keep fighting but any ideas are welcome.

Cheers.