Routing Nested Resource With Has One Relationship? - ruby-on-rails

Each project can have a single page:
resources :project do
resource :page
end
class Project < ActiveRecord::Base
has_one :page
end
class Page < ActiveRecord::Base
belongs_to :project
end
def new
#project = Project.find(params[:project_id])
#page = #project.build_page
respond_to do |format|
format.html
end
end
def create
#project = Project.find(params[:project_id])
#page = #project.build_page(params[:page_id])
respond_to do |format|
if #page.save
format.html { redirect_to #page, :notice => 'Page was successfully created.' }
else
format.html { render action: "new" }
end
end
end
But when I go to save a page, I not only get a routing error, but it doesn't actually save to the db.
Routing Error
No route matches [POST] "/projects/2/pages"
My form action looks like this:
<%= form_for([#job, #page]) do |f| %>
Does anyone have any idea of what is going on? I kind of pieced all of this together from other SO posts, but the more I change a line here or there, I feel like I'm getting further from a working solution. For example, if I change the form action to be:
<%= form_for #page, url: job_page_path(#job) do |f| %>
Everything magically works, but then the edit action is still broken. What basic concept am I butchering?
Thanks!
--Mark

you have a typo:
resource :page
should be
resources :page
(notice the s)
resource (singular) is actually quite a different method that builds a different set of routes. See the docs for more info.
UPDATE / ERRATUM
sorry, i've read your question too fast. you should take a look at Ruby on rails: singular resource and form_for - it seems that form_for does not know how to properly handle singular resources.
Someone here on SO suggests a quick fix for this : nested form_for singular resource

Related

Rails 3 multi nested resource form

I have got such routes:
resources :projects do
resources :chats
resources :lists do
resources :issues
end
end
Now I am trying to setup proper form to add issue to list, but I do not know how... Currently it looks like this:
Controller:
def show
#project = Project.find(params[:id])
#list = List.new
#issue = #list.issues.build
#chats = #project.chats
#lists = #project.lists.includes(:issues)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #project }
end
end
Form
form_for [#list, #issue], remote: true do |f|
And I get error like this:
undefined method `list_issues_path' for #<#<Class:0x00000003996f30>:0x000000038ad678>
How should I solve it? Thanks in advance!
I believe that is because it would need to be nested under the project. If you run rake:routes I imaging you have something like projects/:id/lists/:id/issues/? You can see what the name of the route is next to that. Otherwise you can add the shallow option to the lists route.

Rendering new form for a nested Ruby on Rails Resource

In my Ruby on Rails application, each group has_many :expenses. I have nested my routes, so expenses are entered only as child entities of their parent groups. Here's an excerpt from routes.rb.
resources :groups do
resources :expenses
end
I cannot figure out how to render the 'new' action in the case of an expense not saving when it is submitted through /groups/:group_id/expenses/new. In my expenses_controller.rb, here is how the create action is defined:
def create
#expense = Expense.new(params[:expense])
#expense.group_id = params[:group_id]
if #expense.save
redirect_to group_expense_path(#expense.group.id, #expense.id)
else
render 'new'
end
end
Everything works fine if I satisty expense validation and #expense.save winds up working. However, when it fails and the code tries to render 'new' I get:
undefined method `expenses_path' for #<#<Class:0x007fd408b1fd58>:0x007fd408f21ca8>
So, I am assuming I have something about my nested routing wrong. How do I return the user to the new form but still display to him/her through the flash[] params the errors with the data they originally attempted to submit?
The problem is that #group is not initialized
So in your controller just do
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Looks like you need to explicitly specify the url for form_for in your view.
Something likeā€¦
<%= form_for #expense, :url => group_expenses_path(#group.id) do |f| %>
...
<% end %>
In your <%= form_for %> you have used #group for url, because expenses belongs_to groups. But inside your create action in the controller you have not defined what is #group, so first you should define it as:
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Also I would suggest to use respond_to in your controller:
respond_to do |format|
if #expense.save
format.html { redirect_to group_expense_path(#group.id, #expense.id), :notice => "Any msg you want" }
else
format.html { render :action => "new" }
end
end
All of these are in your create action inside the controller.
Also for different rendering methods look up: http://guides.rubyonrails.org/layouts_and_rendering.html
Hope this helps!

Generating the edit path for a nested resource referenced by multiple models

In routes.rb:
resources :cars do
resources :reviews
end
resources :motorcycles do
resources :reviews
end
In ReviewsController:
before_filter :find_parent
def show
#review = Review.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #review }
end
end
def edit
#review = Review.find(params[:id])
end
# ...
def find_parent
#parent = nil
if params[:car_id]
#parent = Car.find(params[:car_id])
elsif params[:motorcycle_id]
#parent = Motorcycle.find(params[:motorcycle_id])
end
end
Generating the "show" link for a Review is simply (this works):
= link_to "Show", [#parent, #review]
Similarly I would like to reference a generic edit path for a Review, something like (this does not work):
= link_to "Edit", [#parent, #review], :action => 'edit'
Does anyone know if this is possible or, if not, how this might be accomplished?
link_to 'Edit Review', [:edit, #parent, #review]
It turns out the answer I am looking for can be found with the URL helper "edit_polymorphic_path" (see: http://rubydoc.info/github/rails/rails/master/ActionDispatch/Routing/PolymorphicRoutes). In order to get the link I am attempting above I was able to accomplish this with:
edit_polymorphic_path([#parent, #review])
I think what you need here is a polymorphic assocation. Ryan Bates at Railscasts.com explains it perfectly.
http://railscasts.com/episodes/154-polymorphic-association
It will make it easy for you to have things like:
User, Manager, Note
A user can have many notes
A manager can have many notes
A note can belong to a user OR a manager
users/1/notes/edit
managers/1/notes/edit
The Railscast will explain how to do it :)
EDIT:
def edit
#reviewable= find_reviewable
#reviews= #reviewable.reviews
end
private
def find_reviewable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
Then in your link to, it would be something like:
link_to 'Edit Review', edit_review_path([#reviewable, :reviews])
^^ Not tested.

Redirecting to another controller in Rails

I'm trying to redirect from one controller to another in Rails and I am getting this error:
undefined method `call' for nil:NilClass
The code is pretty simple (in def create method):
#blog_post_comment = BlogPostComment.new(params[:blog_post_comment])
respond_to do |format|
if #blog_post_comment.save
flash[:notice] = 'Comment was successfully created.'
redirect_to(#blog_post_comment.blog_post)
else
render :action => "new"
end
end
Save goes ok, the value gets into the database. How can I work around the redirect fail?
Form:
<% form_for #blog_post_comment do |f| %>
<%= f.hidden_field :blog_post_id %>
...
UPD:
After some investigation, it turned out that problem was in the line respond_to do |format| in the blog_post_comment controller. Once I removed it, everything is OK now.
Assuming you have an association, you can find your comment like this:
#blog_post = BlogPost.find(params[:blog_post_id])
#blog_post_comment = #blog_post.comments.build(params[:blog_post_comment])
And then
respond_to do |format|
if #blog_post_comment.save
flash[:notice] = 'Comment was successfully created.'
redirect_to(#blog_post)
else
render :action => "new"
end
end
If you don't have an association, here's how you set it up:
In your BlogPost model, you should have the following line:
has_many :blog_post_comments
And in your BlogPostComment model, you should have:
belongs_to :blog_post
In routes.rb, you should have:
map.resources :blog_post_comment, :has_many => 'blog_post_comments'

Controller related to multiple controllers in rails

This is a tough one I think. I have a comments controller, that I'd like to use for all of my other controllers: such as books, titles, etc.
The problem is, the create action in comments is:
def create
#book = Book.find(params[:book_id])
#comment = #book.comments.create!(params[:comment])
respond_to do |format|
format.html {redirect_to #book}
format.js
end
end
so how can I use the action & comments controller for titles, if its clearly using books attributes?
I'm assuming you have a polymorphic association setup on comment so it can belong to many different types of models? Take a look at this Railscasts episode which shows you how to set that up along with the controller action. Here's the key bit of code.
# comments_controller
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
# routes.rb
map.resources :books, :has_many => :comments
map.resources :titles, :has_many => :comments
map.resources :articles, :has_many => :comments
That's something I've mulled over too. You'd have to make the create method independent of the parent, first of all.
Then you'd have a couple options:
Have a belonsg_to for every thing the comment could possibly be attached to, and selectively fill one of them out.
Have the books/titles/etc belongs_to the comment
I'm fairly new to Rails myself, so I don't know of that's the "correct way" of doing things, maybe someone else has a better solution.
Well, you can't do this generally without being specific about attribute for each of the controllers.
If you wanted to to create a general comments model for a bunch of models in your application, you'd need to pass on the model type that you're commenting to (but not the model itself), and then create a switch that would provide different behaviour based on what you pass.
def create
if params[:type]="book"
#book = Book.find(params[:book_id])
#comment = #book.comments.create!(params[:comment])
respond_to do |format|
format.html {redirect_to #book}
format.js
elsif params[:type]="title"
#title = Title.find(params[:title_id])
#comment = #title.comments.create!(params[:comment])
respond_to do |format|
format.html {redirect_to #title}
format.js
end
end
I think the resource controller is probably what you're looking for. Here's another link on the resource controller.

Resources