form_for for nested routes which namespaced models - ruby-on-rails

I have the following three models:
Article
Article::Line (lines of the article)
Article::Review (reviews of a line)
I want to have a route that is
/articles/11/line/2/review/new
/articles/11/line/2/review/edit
My route.rb
resources :articles do
scope module: 'article' do
resources :lines do
resources :reviews
end
end
end
I am trying to make the form_for work with both new and edit automatically:
<%= form_for [ #line.article, #line, #review ] do |f| %>
However this will produce undefined method `article_article_line_article_reviews_path' error.
What have I done wrong, or is this possible?

Related

Unable to create model based forms rails 4

I have model based form:
<h2>Add New Credit Card</h2>
<%= form_for #credit_card do |f| %>
some fields
<% end %>
routes:
resources :credit_card
credit_card_index GET /credit_card(.:format) credit_card#index
POST /credit_card(.:format) credit_card#create
new_credit_card GET /credit_card/new(.:format) credit_card#new
edit_credit_card GET /credit_card/:id/edit(.:format) credit_card#edit
credit_card GET /credit_card/:id(.:format) credit_card#show
PATCH /credit_card/:id(.:format) credit_card#update
PUT /credit_card/:id(.:format) credit_card#update
DELETE /credit_card/:id(.:format) credit_card#destroy
controller:
def new
#credit_card = CreditCard.new
end
When I try to render by form it says:
undefined method `credit_cards_path' for #<#<Class:0x00000004c37680>:0x00000004c34570>
Did you mean? credit_card_path
credit_card_index_path
credit_card_url
Its a model based form, for now I have nothing in model. I just want to render and submit will go to create method
You're using the Singular Resources:
resources :credit_card
Where you have to use Plural Resources:
resources :credit_cards
In your routes, use plural for resources definition.
resources :credit_cards
That will generate your routes like
credit_cards GET /credit_cards/:id(.:format) credit_card#show
Use resources :credit_cards instead of resources :credit_card

Rails nested Resources not working in controller

I have this nested resource:
resources :services do
resources :users do
put "assign" => "services#users#assign", as: :assign
end
end
My form contains this:
<%= button_to 'submit', service_user_assign_url(service.id, abstractor.id), method: :put %>
this generates the following url, which looks fine to me:
http://localhost:3000/services/1/users/2/assign
and the following is in my services controller:
def assign
#service = Service.find(params[:service_id])
#service.users << User.find(params[:user_id])
redirect_to dashboards_path
end
However I get this error:
The action 'users' could not be found for ServicesController
I'm not sure what this means - I have a has and belongs to many relationship between users and services and I am trying to associate an existing user to a service
You need to tell rails if this action is a member action or a collection action, from the url you mentioned that you want to use, it's a member action:
resources :services do
resources :users do
member do
put :assign
end
end
end

Ruby on Rails: undefined method `' for #<#<Class:>>

I'm relatively new to rails and I've been struggling with this for a couple of days. I'd be much appreciated if you can see where I've gone wrong.
When I view the page in the web browser I get the following message:
Showing C:/Users/Matt/Documents/GitHub/Outputer/app/views/studies/index.html.erb where line #8 raised:
undefined method `studies_path' for #<#:0x6b03808>
8: <%= form_for #new_study do |f| %>
studies_controller:
def index
#line = current_user.lines.find_by_id(params[:line_id])
#machine = #line.machines.find_by_id(params[:machine_id])
#studies = #machine.studies.paginate(page: params[:page], :per_page => 10)
#new_study = #machine.studies.build
end
def create
#study = current_user.lines.machines.study.build(params[:study])
if #study.save
flash[:success] = "Study created"
else
flash[:error] = "Error : Invalid study description"
end
redirect_to :back
end
index.html
....
<section>
<%= form_for #new_study do |f| %>
<div class="field">
<%= f.text_field :description, placeholder: "New study description..." %>
</div>
<%= f.submit "Create", class: "btn" %>
<% end %>
</section>
....
Study Model
....
class Study < ActiveRecord::Base
belongs_to :machine
belongs_to :line
attr_accessible :avg_speed, :avg_uptime, :avg_yield, :description, :duration, :is_active, :start_time, :stop_time, :line_id
validates ....
has_many :events, dependent: :destroy
....
end
....
rake routes:
....
save_line_machine_study PUT /lines/:line_id/machines/:machine_id/studies/:id/save(.:format) studies#save {:has_many=>:machines}
line_machine_studies GET /lines/:line_id/machines/:machine_id/studies(.:format) studies#index {:has_many=>:machines}
POST /lines/:line_id/machines/:machine_id/studies(.:format) studies#create {:has_many=>:machines}
new_line_machine_study GET /lines/:line_id/machines/:machine_id/studies/new(.:format) studies#new {:has_many=>:machines}
edit_line_machine_study GET /lines/:line_id/machines/:machine_id/studies/:id/edit(.:format) studies#edit {:has_many=>:machines}
line_machine_study GET /lines/:line_id/machines/:machine_id/studies/:id(.:format) studies#show {:has_many=>:machines}
PUT /lines/:line_id/machines/:machine_id/studies/:id(.:format) studies#update {:has_many=>:machines}
DELETE /lines/:line_id/machines/:machine_id/studies/:id(.:format) studies#destroy {:has_many=>:machines}
....
routes.rb
resources :users
resources :lines, :has_many => :machines, only: [:index, :edit, :destroy, :show, :create] do
resources :machines, only: [:new, :create, :edit, :update] do
resources :studies
end
end
If I remove the form the page works fine which would suggest its in the form. I've tested the controller commands in the console and they all appear fine - I can create a new study object.
Thanks in anticipation
When you use form_for with a model instance, it defaults to the POST action for that controller which would be your studies_path. This is usually mapped to create in the controller.
From the looks of it, you need to add a route in routes.rb to handle that post request (see resources). You will also need a create method in your studies controller.
Here is a good guide for learning the basics of routing in rails.
Although a missing route is the most common reason for that (not-very-helpful) error, it can also be raised if one or both sides of a has_many/belongs_to relationship is missing or is incorrectly defined. Another place to look is a form field for an attribute that doesn't exist in the related model.
<%= form_for #new_study %> is equivalent to <%= form_for #new_study, url: studies_url %>. As your routes are defined differently, you need to pass the url you'd like to submit the form to to the url parameter (find form_for in the Rails API docs to see what other options it takes).
Three level deep nesting is kind of ugly to maintain, so I'd suggest the following:
resources :users
resources :lines do
resources :machines
end
resources :machines do
resources :studies
end
These shallow routes are much nicer to maintain. There's also a shallow: true option on nested resources calls, see the docs.
In your case:
# With the current setup
<%= form_for #new_study, url: line_machine_studies_path(#line, #machine)
# Same, my preference
<%= form_for [#line, #machine, #new_study] %>
# If you make your routes shallow,
# #line is not nescessary, as #machine holds all information about associations
<%= form_for #new_study, url: machine_studies_path(#machine) %>
# Same, my preference, what I would do
<%= form_for [#machine, #new_study] %>
General suggestions:
#study is preferred over #new_study. #study.new_record? will tell you whether the object is a new record if you need.
There's no has_many :... option on resources routes as far as I'm aware
Google rails shallow routes for more info. Keep nesting to two levels. Think about only what information you really require when creating objects and keep the URLs and url helpers as slim as possible.

has_one association and nested form

I have a model Post:
class Post < ActiveRecord::Base
has_one :draft, class_name: Post, foreign_key: draft_id
end
In routes.rb I have the following:
namespace :admin do
resources :posts do
resource :draft
end
end
What I want to achieve is to enable drafts when using form_for, i.e.:
= form_for [:admin, #post, #draft] do |form|
...where #post and #draft are different instances of the same model.
Right now if I do that, I get an error:
NoMethodError: undefined method `admin_tour_tour_url'
Question: So how do I make the form generate admin_tour_draft_url + with a needed modifiers like edit_ and new_?
P.S. am I going the wrong path here?
specify your own path in the form_tag helper by extracting the route explicitly from your routes:
1) rake routes | grep admin_posts
2) notice the path.. probably something like 'admin_posts_draft'
3) add '_path' to the path and that is the name of your helper.. use that helper in your code
<%= form_tag admin_posts_draft_path(#post, #draft) %>
<% end %>

DRY routing for one polymorph resource with nested resources

Given the following models:
class Blog < ActiveRecord::Base
has_many :posts
end
class SiteBlog < Blog
end
class ProjectBlog < Blog
end
class Post <ActiveRecord::Base
belongs_to :blog
end
And the following routes:
resources :blogs do
resources :posts
end
In say a form partial, the following will work fine if #blog is a Blog:
form_for [#blog, #post] ...
However, if #blog is a ProjectBlog or SiteBlog, it bombs since it will be looking for a URL helper such as project_blog_posts.
I guess something like this would solve this:
[:project_blogs, :site_blogs].each |blogs| do
resources blogs do
resources :posts
end
end
I'm wondering whether there's a way to use the routes for subclassed models (e.g. ProjectBlog) to use the routes of the parent model (Blog). The "as" option only deals with the last object passed like [#blog, #post] to form_for.
Update
As requested below, here are the routes:
resources :blogs, only: [:show] do
resources :posts, only: [:new, :create, :edit, :update]
end
blog_posts POST /blogs/:blog_id/posts(.:format) posts#create
new_blog_post GET /blogs/:blog_id/posts/new(.:format) posts#new
edit_blog_post GET /blogs/:blog_id/posts/:id/edit(.:format) posts#edit
blog_post PUT /blogs/:blog_id/posts/:id(.:format) posts#update
blog GET /blogs/:id(.:format) blogs#show
Update 2:
The tip from an answer below:
form_for [#blog, #post], url: blog_posts_path(#blog, #post) do |f|
This works for "new" actions only, for "edit" actions, I'd get - as expected - a bad URL:
params[:action] # => "edit"
blog_posts_path(#blog, #post) # => "/blogs/publikationsreihe-tafelrunde/posts.5"
So the "if" I mentioned would fix this:
form_for [#blog, #post], url: params[:action]=='new' ? blog_posts_path(#blog, #post) : blog_post_path(#blog, #post) do |f|
But this looks incredibly clumsy, there must be a better way.
Easily solvable by passing the resource url to the form:
<%= form_for [#blog, #post], :url => blog_posts_path(#blog, #post) do |f| %>
...
<%- end %>

Resources