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

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.

Related

Rails Custom route, how to remove ?id=

My app has a model msa with a :name and :short_name.
On the msa index page, there is a drop-down menu of all msa.names so the user can route to the show page of the selected msa.
This functionality is coded with a collection_select that routes to a custom method in the msa controller.
msa index view:
<%= form_with url: 'msas/redirect', method: :post, local: true do |f| %>
<%= f.collection_select(:id, Msa.all, :id, :name)%>
<%= f.submit "Search" %>
<% end %>
msa controller redirect method:
def redirect
#msa=Msa.find(params[:id])
redirect_to msa_path(#msa, short_name: #msa.short_name, id: #msa.id)
end
Rather than have routes that read localhost:3000/msa/1, I want them to read localhost:3000/search/:short_name.
My routes.rb:
scope format: false do
resources :msas, :only => [:show], path: '/search', param: :short_name
end
resources :msas, :except => [:show]
Everything seems to work ok, except the route appears like this in the browser:
localhost:3000/search/Chicago?id=1
I tried remove the id: #msa.id from the redirect method, but ended up with this error:
Couldn't find Msa without an ID
which was keyed off the set_msa method in the msa controller.
I'm wondering two things: a) have I strayed too far from typical rails convention, and b) if there is a way to do this and not reveal the id# of my msa models to the world?
If there is a way to do this and not reveal the id# of my msa models
to the world?
Just remove the id from the path helper
redirect_to msa_path(#msa, short_name: #msa.short_name)
and counter the error by defining a new method and removing the entry for show method in the set_msa like so
before_action :set_custom_msa, only: [:show]
before_action :set_msa, only: [:edit, :update,..] #remove the entry for show
private
def set_custom_msa
#msa = Msa.find_by(short_name: params[:short_name])
end
Have I strayed too far from typical rails convention
No! but if your final goal is to hide :id from the URL and to make user-friendly URLs, then I would suggest you to look at friendly_id

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

form_for for nested routes which namespaced models

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?

modifying a user model with a rails form

total rails noob here so bear with me. I've been stuck for a long time trying to figure out how to make a page that allows a logged-in user to change, via text field, certain attributes of the user model. For instance, my user model has a number of "measurement" attributes that i'd like to directly modify via the page. My user model is running on Devise--I'm not sure if i'm doing the right thing by modifying the user model directly.
Here's my controller. I set it to find the second user because I was simply testing it out to see if it worked.
class MeasurementsController < ApplicationController
def index
#person = User.find(2)
end
end
Here's my index.html.erb:
<%= form_for #person do |f| %>
<%= f.label :style %>:
<%= f.text_field :style %><br />
<%= f.submit "Update"%>
<% end %>
However, it spits out this error:
NoMethodError in Measurements#index
undefined method `user_path'
Any help would be REALLY appreciated. Thanks so much in advance. Really lost here.
EDIT: Here's my routes.rb:
Contourfw::Application.routes.draw do
ActiveAdmin.routes(self)
devise_for :admin_users, ActiveAdmin::Devise.config
devise_for :users
root :to => 'contourfw#landingpage'
match "measurements" => "measurements#index"
match "styles" => "styles#index"
end
It looks like you need to add a resourceful route in your config/routes.rb:
resources :users
Update
By default that will assume a controller named users_controller.rb. If you want it to use the controller above, amend that to:
resources :users, :controller => "measurements"
See http://guides.rubyonrails.org/routing.html#customizing-resourceful-routes for more details.

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