Rails restful routes problem - ruby-on-rails

I've got two models: Book and ReadingList. A ReadingList has_and_belongs_to_many Books. On the BooksController#show page, I'd like to have a select list that shows all the reading lists, with a button to add the current book to the selected reading list.
Presumably this should go to the ReadingListController#update action, but I can't specify this as the form's URL, because I won't know which ReadingList to send to at the time the form is created. I could hack it with JavaScript, but I'd rather not rely on that.
Would it be better to have a custom action in the BooksController that accepts a reading list id to add the book to, or can I work the routes so this request ends up getting to the ReadingListController#update action?

I suggest that you have a resource which is a ReadingListEntry that represents a book in a reading list. Then you can simply POST to that resource to add it. There doesn't actually need to be a model behind it, you can manipulate the reading list directly.

Obviously this is something that could easily be achieved by using Ajax to submit the form, but in the case where JavaScript is disabled / unavailable, your best option is to have a custom action in the BooksController that adds it to the required reading list.
You could combine both by having the form pointing to the action in the BooksController, but having an onsubmit handler that posts to the ReadingList controller via Ajax.

I would create a custom action and route such that you can provide a book_id and list_id and form the relation.
Assuming you're using restful routes
resources :books do
post '/lists/:list_id/subscribe' => 'lists#subscribe', :as => :subscribe
end
def subscribe
#list = List.find params[:list_id]
#book = Book.find params[:book_id]
#list << #book
end
Now you can use button_to with or without ajax.

Perhaps a has_many :through relationship would be better? I like Anthony's idea of a ReadingListEntry resource - perhaps put a model behind this giving you:
# models/book.rb
has_many :reading_list_entries
has_many :reading_lists, :through => :reading_list_entries

I think here you are changing the Book, not the ReadingList. Therefore you should PUT to the BooksController#update resource with a new list_id attribute.
# in views/books/show.html.erb
<%= form_for #book, :url => book_path(#book) do |f| =>
<%= f.select :list, ReadingList.all.map { |l| [l.name, l.id] } =>
<%= submit_tag "Change" =>
<% end %>
# in controllers/books_controller.rb
# params[:book][:list_id] => 123
def update
#book = Book.find(params[:id])
#book.update_attributes(params[:book])
end
# config/routes.rb
resources :books
resources :lists do
resources :books
end
If you wanted a Book to belong to more than one ReadingList you'd need a has_and_belongs_to_many relationship instead

Related

How to manage nested routes and controllers in rails?

We have multiple models Post, Blog, Wiki and Comment.
In the comment table we maintain object_type, object_id and comment_type.
Comment table data
id object_type object_id comment_type
1 'Post' 1 'System'
2 'Blog' 2 'System'
3 'Wiki' 3 'User'
4 'Wiki' 4 'System'
/post/1/comments/:comment_type
/wiki/1/comments/:comment_type
To handle this, How should my routes should look like and how many controller should I create to handle different comment types ?
You can get the parent model from the url:
before_filter :get_commentable
def create
#comment = #commentable.comments.create(comment_params)
respond_with #comment
end
private
def get_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.pluralize.classify.constantize.find(id)
end
As for this:
/post/1/comments/:comment_type
nesting the resources as in so:
resources :posts do
resources :comments
end
gives you:
post_comment_path GET /posts/:post_id/comments/:id(.:format) comments#show
you can do the same thing for the other resources.
UPDATE:
about selecting comment_type, for example, one way to handle it is to create a separate model.
class Comment
has_and_belongs_to_many :comment_types
end
class CommentType
has_and_belongs_to_many :comments
end
i you are using simple_form, in your new.html.erb form, you will do this:
<%= f.association :comment_types %>
this will give you a drop down to select the comment_types. you can create the comment types in your console. say you have only: "system" and "user" comment_types. simply create those in the console and they will both show up in the drop down for you to select.
If you take this approach, you don't need to nest the resources in your routes.rb file.

Rails use nesting and resource path with other model

I have such method controller:
class Admin::CarManufacturersController < ApplicationController
def edit
#man = Manufacturer.find(params[:id])
render :layout => 'admin'
end
def update
#man = Manufacturer.find(params[:id])
if #man.update_attributes(params[:car_manufacturer])
****
else
render :action => :edit, :layout => 'admin'
end
end
end
and i have such route:
namespace :admin do
resources :car_manufacturers do
###
end
end
and such form partial:
= form_for [:admin, #man] do |f|
###
but when i call this form to edit my data i get:
undefined method `admin_manufacturer_path'
but i need admin_car_manufacturer_path i thing it's becouse i use other model name in controller, but i can't change it... how can i use right pass? i try to write admin_car_manufacturer_path in form, but i think this is bad idea. How to solve my problem?
I would think about renaming your controller/your model to match. Both should either be just manufacturer or car manufacturer. Having the same names for a resource's controller and model will spare you problems like the one you're having right now.
In any case, if you just need a quick fix, you can get around this by specifying the as option for your nested routes like this:
namespace :admin do
resources :manufacturers, as: :car_manufacturers do
###
end
end
Source: Rails Routing from the Outside In - Ruby on Rails Guides - 3.6: Naming Routes
That will turn your path names into admin_car_manufacturer_path etc and should allow you to use your form the way you you intended to. But I really recommend renaming your model and controller so that they match.

Rails 3.1 Post/Comment model for blog -- How to submit comments via ajax?

I'm trying to learn about using remote forms and using the example of an app where the Post model has_many Comments.
Assuming i have used the generic rails scaffolding to create/setup the post/comment model, controllers, default views, routes, etc, -- what should my app/views/posts/show.html look like?
Specific i am confused about:
Where should the comment form post to?
What parameters should be included?
Do i NEED to use a hidden attribute such as f.hidden_field :post_id, :value => #post.id
Thank you!
Assuming that your post has_many comments...
routes.rb (Nested Resources)
resources :posts do
resources :comments
end
In your comments_controller
def create
#post = Post.find(params[:post_id]
#comment = #post.comment.build(params[:comment])
if #comment.save
...
end
In the form:
=form_for [#post, #comment], :remote => true do |f|
=f.text_field :text
=f.submit

Any way around putting hidden field in forms for resources with belongs_to association

I'm learning Rails by writing simple TODO tasks aplication.
Two models are:
class List < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
# ...
end
class Task < ActiveRecord::Base
belongs_to :list
# ...
end
Tasks are routed as a nested resources under Lists. So when a new Task is created by user a POST message is sent to /lists/:list_id/tasks. So far in Tasks#new view's form there is
f.hidden_field :list_id, :value => params[:list_id]
but it's a terrible solution, because anyone can change value of that hidden field.
What is the convention here? Should I put something like
#task.list_id = params[:list_id]
in Tasks#create action and get rid of the hidden field, or maybe
#task = List.find(params[:list_id]).tasks.new(params[:task])
if #task.save
# ...
end
or there is even a better way I don't know about?
Edit:
Yeah, well there was similar question and its answer is pretty much covering my question. If you have different one please post it.
You're right - that would be horrible. No need for hidden fields. Something like the following.
In your TasksController:
def new
#list = List.find(params[:list_id])
#task = #list.tasks.build
end
def create
#list = List.find(params[:list_id])
#task = #list.tasks.new(params[:task])
# etc
end
In your Task#new view:
<% form_for [#list, #task] ... %>
...
<% end %>
If you are concerned about security (like one user creating to-dos in another user's lists - and I assume you are, because you didn't want to use a hidden field stating that anyone can change value of that hidden field), I don't see how #bjg solution is any better then yours, since you're getting #list from params anyways, and anybody can manipulate params on the browser (changing the URL to post to is as easy as changing the hidden field value).
One common way to solve this without having to implement a more complex permission solution is to just use current_user association's, like this:
def new
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.build
end
def create
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.new(params[:task])
# etc
end
This way, no matter what is the value of params[:list_id] (it could have been manipulated by the user), you can rest assured the #task will end up on that user's account, since #list will only find a record that belongs to current_user.
You can evolve this in a real-world app by returning an error message if #list is not found.

Creating a second form page for a has_many relationship

I have an Organization model that has_many users through affiliations.
And, in the form of the organization ( the standard edit ) I use semanting_form_for and semantic_fields_for to display the organization fields and affiliations fields.
But I wish to create a separete form just to handle the affiliations of a specific organization. I was trying to go to the Organization controller and create a an edit_team and update_team methods then on the routes create those pages, but it's getting a mess and not working.
am I on the right track?
Yes, you should create edit_team and update_team methods in controller and add them into routes.rb
#organizations_controller
def edit_team
#organization = Organization.find(params[:id])
#team = #organization.affiliations
end
def update_team
# updating affiliations
end
#routes.rb
map.resources :organizations, :member => { :edit_team => :get, :update_team => :put }
and this is enough. So show errors why it isn't working.

Resources