How to manage nested routes and controllers in rails? - ruby-on-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.

Related

Rails 4 - routing actions for contact form

I have two actions in the controller:
def report
#user = User.find_by_slug(params[:slug])
end
def reportForm
#user = User.find_by_slug(params[:slug])
Thread.new do
mail = ...
end
#message = 'Thanks!'
end
and in routes:
# User report form
get "/user/:slug/report", to: "users#report"
# Catch report form and action
post "/user/:slug/report", to: 'users#reportForm'
And the view:
<form method="POST" action="/user/<%= #user.slug %>/reportForm">
...
But the problem is, that when I send the form, the action reportForm is not called and instead of that is only refresh the current page with the form.
What's wrong here?
Thank you guys.
Form Helpers
The first thing that's wrong is you're not using the form helpers that Rails provides - this is a problem because you'll end up with niggly little problems like the one you're receiving:
#config/routes.rb
resources :users do
get :report #-> domain.com/users/:id/report
post :reportForm #-> domain.com/users/:id/reportForm
end
#view
<%= form_tag user_reportForm_path(#user) do %>
...
<% end %>
Routes
The second issue you have is to do with your routes
You've set the following routes:
get "/user/:slug/report", to: "users#report"
post "/user/:slug/report", to: 'users#reportForm'
This means you've got to send the request to domain.com/user/user_slug/report. Your form sends the URL to reportForm...
You should see my routes above for the solution to this problem
But more importantly, you should read up on nested resources:
#config/routes.rb
resources :users do
match :report, action: "reportForm", via: [:get, :post] #-> domain.com/users/:id/report
end
Slug
Finally, you're trying to use params[:slug] in your controller
With the resourceful routes you should be using in Rails, you'll be passing params[:id] most of the time. This should not be an issue (what is contained in params[:id] can be anything).
I would highly recommend looking at a gem called friendly_id, which makes including slugs in your application a lot simpler:
#app/models/user.rb
Class User < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
end
This will allow you to call:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def reportForm
User.find params[:id] #-> will use either `id` or `slug`
end
end

Form for 3 association models

I am still a newbie so please forgive this silly question. I got 3 models:
- User (generated by devise), Comment and Post
User has many posts and comments
Comment belongs to both post and user
Post has many comments and belongs to user
My routes.rb
resources :users do
resources :posts do
resources :comments
end
end
My form code:
<%= form_for([#user,#post,#comment]) do |f| %>
...
<% end %>
I want to generate to user_post_comments_path but the above form_for generate to post_comments_path. Why? Did I misunderstand something. Thanks alot
The Rails routing and Form handling is very confusing and I still get it wrong all the time...
I think that your problem is that some of your variables is not set (nil) and so rails can not determine what you are actually up to.
I would also like to recommend to you that you don't nest your routes like that unless you have to for the sake of the urls.
It's usually enough to nest one level deep and use the current_user to assign to models when creating them. This also reduces the security risk involved when posting ids of other users:
def create
#post = current_user.posts.build(post_params)
[...]
end
def create
#comment = current_user.comments.build(comment_params)
#comment.post = Post.find params[:post_id]
[...]
end

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 restful routes problem

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

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