STI and form_for problem - ruby-on-rails

I am using Single Table Inheritance for managing different types of projects.
Models:
class Project < ActiveRecord::Base
end
class SiteDesign < Project
end
class TechDesign < Project
end
Edit action from projects_controller:
def edit
#project = Project.find(params[:id])
end
View edit.html.erb:
<% form_for(#project, :url => {:controller => "projects",:action => "update"}) do |f| %>
...
<%= submit_tag 'Update' %>
<% end %>
Update action of projects_controller:
def update
#project = Project.find(params[:id])
respond_to do |format|
if #project.update_attributes(params[:project])
#project.type = params[:project][:type]
#project.save
flash[:notice] = 'Project was successfully updated.'
format.html { redirect_to(#project) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #project.errors, :status => :unprocessable_entity }
end
end
end
Then i do some edits of TechDesign entry on edit view and get error:
NoMethodError in ProjectsController#update
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
In parametrs it is obvious that instead of project parameter name i have tech_design
Parameters:
{"commit"=>"Update",
"_method"=>"put",
"authenticity_token"=>"pd9Mf7VBw+dv9MGWphe6BYwGDRJHEJ1x0RrG9hzirs8=",
"id"=>"15",
"tech_design"=>{"name"=>"ech",
"concept"=>"efds",
"type"=>"TechDesign",
"client_id"=>"41",
"description"=>"tech"}}
How to fix it?

Here's the source of your problem. This is setting #project as an instance of a TechDesign object.
def edit
#project = Project.find(params[:id])
end
You can ensure things work the way you want by specifying :project for a name in the form_for call.
<% form_for(:project, #project, :url => {:controller => "projects",:action => "update"}) do |f| %>
...
<%= submit_tag 'Update' %>
<% end %>

For Rails 3
<% form_for(#project, :as => :project, :url => {:controller => "projects",:action => "update"}) do |f| %>
...
<%= submit_tag 'Update' %>
<% end %>

A random note: If you are using single table inheritance (STI) and forget to remove the initialize method from your subclass definitions you will get a similar "nil object when you didn't expect it" exception.
For example:
class Parent < ActiveRecord::Base
end
class Child < Parent
def initialize
# you MUST call *super* here or get rid of the initialize block
end
end
In my case, I used my IDE to create the child classes and the IDE created the initialize method. Took me forever to track down...

For Rails 4, I have confirmed the only thing that has seemed to work for me is to explicitly specify the URL and the AS parameters:
<% form_for(#project, as: :project, url: {controller: :projects, action: :update}) do |f| %>
...
<% end %>
Seems ugly to me!

Related

Rails 4 form_for double nested comments

First of all, I've found some very helpful answers already (see links below), but I'm still struggling to make this work. I haven't been programming for long, and this is definitely stretching what I feel comfortable claiming to understand.
I have three models. For example's sake, I'll call them Tree, Bark, Engraving (the comments). I'm trying to add a simple form partial for users to submit new comments (engravings).
The below setup is producing the error:
NoMethodError at /trees/1
undefined method `tree' for nil:NilClass
Controllers
class TreesController < ApplicationController
def show
#barks = Bark.where(tree_id: #tree.id)
#engraving= Engraving.new( :bark=> #bark)
end
end
class BarksController < ApplicationController
def show
#engravin= Engraving.new( :bark=> #bark)
#engravings = Engraving.where(bark_id: #bark.id)
end
end
class EngravingsController < ApplicationController
def show
#bark= Bark.find(params[:bark_id])
#engraving = Engraving.new(params[:engraving])
end
end
def create
#bark = Bark.find(params[:bark_id])
#engraving = Engraving.new(params[:engraving])
#engraving.user_id = current_user.id
respond_to do |format|
if #engraving.save
format.html { redirect_to tree_bark_path(#tree, #bark), notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #engraving}
else
format.html { render action: 'new' }
format.json { render json: #engraving.errors, status: :unprocessable_entity }
end
end
end
Models
class Tree < ActiveRecord::Base
belongs_to :user
has_many :barks
has_many :engravings, through: :barks
end
class Bark< ActiveRecord::Base
belongs_to :user
belongs_to :tree
has_many :engravings
delegate :title, :link, :to => :tree, :prefix => true
def new_engraving_path
Rails.application.routes.url_helpers.new_tree_bark_engraving_path(book, self)
end
def serializable_hash(*args)
super.merge 'new_engraving_path' => new_engraving_path
end
end
class Engraving< ActiveRecord::Base
belongs_to :user
belongs_to :bark
delegate :tree, :to => :bark, :prefix => true
def to_edit_path
Rails.application.routes.url_helpers.edit_tree_bark_engraving_path bark_tree, bark, self
end
def to_path
Rails.application.routes.url_helpers.tree_bark_engraving_path bark_tree, bark, self
end
def serializable_hash(*args)
super.merge 'path' => to_path, 'edit_path' => to_edit_path
end
end
Calling the Form in a View
On the Tree show page, I have all the Bark populating there. Clicking on an Bark element opens a modal, where the Bark.content and Engravings are shown. Under the last Engraving, I want to have a text_area form to create a new Engraving, and add it to this "piece" of bark.
# trees\show.html.erb
<%= render #barks %>
# barks\_bark.html.erb
<%= render partial: 'barks/modal', locals: { bark: bark} %>
# barks\modal.html.erb
<% render partial: '/engravings/simpleForm', :collection => bark.engravings %>
# Form partial
<%= form_for #engraving, :url => (new_tree_bark_engraving_path(:tree_id => #bark.tree, :bark_id => #bark.id)) do |f| %>
<%= f.hidden_field :bark_id %>
<div class="field">
<%= f.text_area :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Routes
get "pages/home"
resources :trees do
collection do
get 'search'
post 'load_from_api'
end
resources :barks do
resources :engravings
end
end
resources :barks do
resources :engravings
end
root "trees#index"
Other Answer Sources
Rails Routing Error for nested form_for
Twice Nested Resource Form_For Issue
form_for with nested resources
Your error message NoMethodError at /trees/1 undefined method 'tree' for nil:NilClass means that somewhere you must be calling .tree on a nil object. The only place I can see that happening is in the form partial. I'm assuming that #bark.tree is failing.
From your question it is not clear which controller you are rendering the partial from, but the most likely cause is that #bark is not being passed into the form. Make sure that you are passing #bark into your partial, here is an example:
<%= render partial: "form", locals: {bark: #bark} %>
You're definining #barks in your trees#show action, but never #bark
def show
#barks = Bark.where(tree_id: #tree.id)
#engraving= Engraving.new( :bark=> #bark)
end
So #bark is nil.

Rails: Updating Nested attributes - undefined method `to_sym' for nil:NilClass

Edit: Added the update action, and on what line the error occurs
Model:
class Match < ActiveRecord::Base
has_and_belongs_to_many :teams
has_many :match_teams
has_many :teams, :through => :match_teams
accepts_nested_attributes_for :match_teams, :allow_destroy => true
end
Controller:
def new
#match = Match.new
#match_teams = 2.times do
#match.match_teams.build
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #match }
end
end
def update
#match = Match.find(params[:id])
respond_to do |format|
if #match.update_attributes(params[:match])
format.html { redirect_to #match, notice: 'Match was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
Nested model:
class MatchTeam < ActiveRecord::Base
belongs_to :match
belongs_to :team
end
Association:
class Team < ActiveRecord::Base
has_and_belongs_to_many :matches
end
View:
<%= form_for(#match) do |f| %>
<%= f.fields_for :match_teams, #match_teams do |builder| %>
<%= builder.collection_select :team_id, Team.all, :id, :name, :include_blank => true %>
<% end %>
<% unless #match.new_record? %>
<div class="field">
<%= f.label :winning_team_id %><br />
<%= f.collection_select :winning_team_id, #match.teams, :id, :representation %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Params:
Processing by MatchesController#update as HTML
Parameters: {"utf8"=>"Ô£ô", "authenticity_token"=>"QIJChzkYOPZ1hxbzTZS8H3AXc7i
BzkKv3Z5daRmlOsQ=", "match"=>{"match_teams_attributes"=>{"0"=>{"team_id"=>"1", "
id"=>""}, "1"=>{"team_id"=>"3", "id"=>""}}, "winning_team_id"=>"3"}, "commit"=>"
Update Match", "id"=>"2"}
Creating a new match with 2 teams work fine, the edit view also shows the correct values, but the update action gives me this error.
undefined method `to_sym' for nil:NilClass
app/controllers/matches_controller.rb:65:in `block in update'
line 65: if #match.update_attributes(params[:match])
I've figured it out. I read that a join table like MatchTeams doesn't need an ID. I'm guessing this is true when not doing any nested forms. I redid my migration removing the exclusion of the id column, and now everything works fine. Don't we all love this stupid errors? :)
Without seeing the offending to_sym in your code, just know that the thing it's attached to has not been defined properly. If this is a variable such as #var.to_sym, you most likely:
Haven't set #var at all
Set it but it's returning nil because there are no matches (e.g. #var = #project.companies.first but #project has no companies tied to it).
You are missing a relevant bit of data in your params. If your to_sym is relying on data submitted through the form, it won't work if the user leaves out the bit of data you're assuming. In this case, you should test first to see if the data was entered before running .to_sym on it.

In Rails, how do you call a specific method in a controller from a form in a view?

new to rails so bear with me. What I would like to do, is I have a list of messages on the screen. I want the message to be marked as 'closed' which means it just fills in a completeDate.
I have a model:
class Message < ActiveRecord::Base
attr_accessible :topic, :body, :completeDate, :created_at
belongs_to :account
belongs_to :user
has_many :message_followups
end
I have a controller:
class MessagesController < ApplicationController
def close
#message = Message.find(params[:messageId])
#message.completeDate = Date.today
if #message.save
redirect_to myHomeMessages_path, :flash => { :success => "Your message was closed." }
else
redirect_to myHomeMessages_path, :flash => { :error => "Error closing message." }
end
end
end
I have a view:
<%= form_for (???) do |f| %>
Are you sure you wish to close this message?<br>
<%= hidden_field_tag 'messageId', message.id.to_s %>
<%= submit_tag "Close Message" %>
<% end %>
I'm having a problem figuring out how to get the form_for or a form_tag to call the specific method 'close' in the messages controller. Any help would be greatly appreciated, thanks.
Matt
A very important hint, you should not use camel case for variable names on Ruby, variable sand methods should use underscore as separators, if you're coming from another language like Java, try to avoid using the same naming patterns you used in there. Camel case in ruby is only for class and module names.
I am guessing you're using Ruby on Rails 3, as you haven't said what you're using. First, you need a route for that, it would look like this on your routes.rb file:
resources :messages do
member do
post 'close'
end
end
A little change to your controller
def close
#message = Message.find(params[:id]) #you don't need to use the hidden field here
#message.completeDate = Date.today
if #message.save
redirect_to myHomeMessages_path, :flash => { :success => "Your message was closed." }
else
redirect_to myHomeMessages_path, :flash => { :error => "Error closing message." }
end
end
And your form is going to look like this:
<%= form_for( #message, :url => close_message_path( #message ), :html => { :method => :post } ) do |f| %>
Are you sure you wish to close this message?<br>
<%= f.submit "Close Message" %>
<% end %>
This form is going to be posted to "/messages/ID_HERE/close" and Rails is going to set the "ID_HERE" value as the "id" parameter on your request.
You can see the full documentation about form_for here.
Something like this should square things away for you:
<%= form_for #message, :url => { :action => "close" }, :html => { :method => :put } do |f| %>
What this does is specify that you are performing an HTTP PUT method to the close method in the messages controller. For more information about the form_for helper method read here
Do you have an update method in your controller, if not you can just do this:
<%= form_for( #message ) do |f|
<p>Are you sure you wish to close this message?</p>
<%= hidden_field_tag :close %>
<%= f.submit "Close Message" %>
<% end %>
Then in your controller:
class MessagesController < ApplicationController
def update
#message = Message.find(params[:id])
#message.completeDate = Date.today if params.has_key?(:close)
if #message.save
redirect_to myHomeMessages_path,
:flash => { :success => "Your message was closed." }
else
redirect_to myHomeMessages_path,
:flash => { :error => "Error closing message." }
end
end
end

Rails: Form for selecting an existing parent when creating new child records?

I have a has_many and belongs_to association set up between two models: Project and Task.
I'd like to be able to create a form which enables me to create a new Task and assign an existing Project as a parent. For example, this form might have a pulldown for selecting from a list of existing projects.
There are only a finite set of projects available in this application, so I've created Project records via a seeds.rb file. I do not need to make a form for creating new Projects.
I believe I've achieved a solution by using a collection_select form helper tag in the new Task form. I'm pretty happy with how this works now, but just curious if there are other approaches to this problem.
#models/project.rb
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
end
#models/task.rb
class Task < ActiveRecord::Base
belongs_to :project
end
#controllers/tasks_controller.rb
class TasksController < ApplicationController
def new
#task = Task.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #task }
end
end
def create
#task = Task.new(params[:task])
respond_to do |format|
if #task.save
format.html { redirect_to(#task, :notice => 'Task was successfully created.') }
format.xml { render :xml => #task, :status => :created, :location => #task }
else
format.html { render :action => "new" }
format.xml { render :xml => #task.errors, :status => :unprocessable_entity }
end
end
end
end
#views/new.html.erb
<h1>New task</h1>
<%= form_for(#task) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="select">
<%= collection_select(:task, :project_id, Project.all, :id, :name) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= link_to 'Back', tasks_path %>
I just reviewed your code and this looks fantastic to me. One small tweak:
<%= f.collection_select(:project_id, Project.all, :id, :name) %>
This is just slightly cleaner in that you're still using the |f| block variable
Since you mentioned other approaches, I would definitely mention and actually recommend, you use formtastic. The associations are handled automatically and keeps your code clean and also gives you some great customization options.

validates_presence_of not working properly...how to debug?

In my Review model, I have the following:
class Review < ActiveRecord::Base
belongs_to :vendor
belongs_to :user
has_many :votes
validates_presence_of :summary
end
I submit a new entry as follows in the URL:
vendors/9/reviews/new
The new.html.erb contains a form as follows:
<%= error_messages_for 'review' %>
<h1>New review for <%= link_to #vendor.name, #vendor%></h1>
<% form_for(#review, :url =>vendor_reviews_path(#vendor.id)) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :summary %><br />
<%= f.text_area :summary, :rows=>'3', :class=>'input_summary' %>
<%= f.hidden_field :vendor_id, :value => #vendor.id %>
</p>
<p>
<%= f.submit 'Submit Review' %>
</p>
<% end %>
When I leave the field for :summary blank, I get an error, not a validation message:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.name
Extracted source (around line #3):
1: <%= error_messages_for 'review' %>
2:
3: <h1>New review for <%= link_to #vendor.name, #vendor%></h1>
I don't understand what is happening, it works if :summary is populated
def new
#review = Review.new
#vendor = Vendor.find(params[:vendor_id])
#review = #vendor.reviews.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #review }
end
end
def create
#review = Review.new(params[:review])
##vendor = Vendor.find(params[:vendor_id]) #instantiate the vendor from the URL id -- NOT WOKRING
##review = #vendor.reviews.build #build a review with vendor_id -- NOT working
#review = #current_user.reviews.build params[:review]#build a review with the current_user id
respond_to do |format|
if #review.save
flash[:notice] = 'Review was successfully created.'
format.html { redirect_to review_path(#review) }
format.xml { render :xml => #review, :status => :created, :location => #review }
else
format.html { redirect_to new_review_path(#review) }
format.xml { render :xml => #review.errors, :status => :unprocessable_entity }
end
end
end
My guess is that when it fails it is going to redirect_to new_review_path(#review) and so doesn't know the vendor it. How can I redirect to vendor/:vendor_id/reviews/new instead?
You probably don't have #vendor member variable set - but to fix this, it would be more correct to use not #vendor directly, but through your #review variable instance.
If you are creating new review, you already have #review member variable created, and you simply are populating fields in it - so, you need to set the vendor for #review (unless it's optional)... it would be more correct to use #review.vendor.name instead.
(If vendor is optional, then you obviously must catch all vendor.nil? cases.)
What code do you have in the new and create actions in your ReviewsController?
I suspect that your new Review is failing validation because the summary field is blank and then when the form is redisplayed on validation failure, the #vendor instance variable is nil.
You need to make sure that #vendor is assigned a value for both code paths.
I think you need to render :action => 'new' instead of your redirect_to new_review_path(#review). This will keep your error_messages on the #review object. By redirecting you are losing the old object and creating a new one.
As others has said, you also need to make sure you re-populate the #vender variable in your create method before rendering the view.
PS. I like to use the ardes resources_controller plugin for bog standard controller actions like these, makes life a lot easier for me and it handles nested resources really well.

Resources