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
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.
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!
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.
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'
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.