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.
Related
I have two models: Personand User. A person can have a single user or no user at all, but every user have to be belong to a person.
I have set both models like so:
Person (has a has_user attribute):
has_one :user, dependent: :destroy
accepts_nested_attributes_for :user
User (has person_id attribute):
belongs_to :person
validates_presence_of :login, :password
validates_uniqueness_of :login
In the Person form I have a nested form_for that only shows up if I check a check box that also set the has_user attribute to true on the Person's form part.
My issue is that whenever I submit a person that has no user, it still tries to validate the user. How can I make it work?
UPDATE:
In my view:
<div class="form-group">
<%= person_form.label :has_user, :class => 'inline-checkbox' do %>
Possui usuário
<%= person_form.check_box :has_user, {id: "hasUser", checked: #person.has_user} %>
<% end %>
</div>
</div>
<div id="userPart" class="findMe"
<% if #person.user.id.blank? %> style="display:none;"
<% end %>>
<h2> User: </h2>
<div class="container">
<%= person_form.fields_for :user do |user_form| %>
<%= render partial: 'users/campos_user', locals: {form: user_form} %>
<% end %>
</div>
</div>
In my Person.js:
jQuery ->
$(document).ready ->
$(document).on 'turbolinks:load', #Added line
console.log("Turbolinks ready.")
$("#hasUser").change ->
$("#userPart").toggle();
return
return
return
return
In my Person Controller:
def new
#person= Contato.new
#person.ativo = true
#page_title = 'Novo person'
#person.build_user
end
def create
#person = Person.new(person_params)
respond_to do |format|
if #person.save
flash[:notice] = 'Contato foi criado com sucesso.'
if #person.has_user == true
format.html {redirect_to controller: :users, action: :new, person_id: #person.id}
else
format.html {redirect_to #person}
end
format.json {render :show, status: :created, location: #person}
else
flash[:warn] = "Erro ao criar contato."
format.html {render :new}
format.json {render json: #person.errors, status: :unprocessable_entity}
end
end
end
Person's params:
def person_params
params.require(:person).permit(:id, :company_id,
:active, :name,
:cargo, :celular,
:email, :nascimento,
:observacoes, :mensagem_instantanea,
:tipo_msg_inst, :possui_usuario,
user_attributes: [:login, :password, :permits, :id, :person_id, :_destroy])
end
You can reject submitting user attributes by using reject_if option
accepts_nested_attributes_for :user, :reject_if => :no_user_connected
def no_user_connected(attributes)
attributes[:user_attributes][:login].blank?
end
This should discard the user part of the form (all user attributes). Please note that I dunno how your form or controller looks like, so you might need to build the condition differently.
You can also make the validations on user conditional. But overall the best way to avoid convoluted validation problems is to use some kind of form objects where you encapsulate the validations process. https://thoughtbot.com/blog/activemodel-form-objects
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.
I'm trying to get the text from a text_area field in a form to save to a database in a different Model with the current Model's ID.
Currently, this works but only will save integers. If I put text into the 'Notes' field, then its saves it as a '0'. I suspect this is working correctly but I'm missing a piece to my puzzle. This is because I only want the 'Ticket' to save the note_id because I will have multiple 'Notes' per 'Ticket.
How can I get the Note to save in the Note Model, with an ID, and associate that note_id with this specific ticket?
Form - /app/views/tickets/_form.html.erb
<%= form_for(#ticket) do |f| %>
<% if #ticket.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#ticket.errors.count, "error") %> prohibited this ticket from being saved:</h2>
<ul>
<% #ticket.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note, :size => "101x4", :placeholder => "Leave notes here." %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Tickets_controller.rb
class TicketsController < ApplicationController
# GET /tickets
# GET /tickets.json
def index
#tickets = Ticket.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #tickets }
end
end
# GET /tickets/1
# GET /tickets/1.json
def show
#ticket = Ticket.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/new
# GET /tickets/new.json
def new
#ticket = Ticket.new
#ticket.notes.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/1/edit
def edit
#ticket = Ticket.find(params[:id])
end
# POST /tickets
# POST /tickets.json
def create
#ticket = Ticket.new(params[:ticket])
respond_to do |format|
if #ticket.save
format.html { redirect_to #ticket, notice: 'Ticket was successfully created.' }
format.json { render json: #ticket, status: :created, location: #ticket }
else
format.html { render action: "new" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# PUT /tickets/1
# PUT /tickets/1.json
def update
#ticket = Ticket.find(params[:id])
respond_to do |format|
if #ticket.update_attributes(params[:ticket])
format.html { redirect_to #ticket, notice: 'Ticket was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# DELETE /tickets/1
# DELETE /tickets/1.json
def destroy
#ticket = Ticket.find(params[:id])
#ticket.destroy
respond_to do |format|
format.html { redirect_to tickets_url }
format.json { head :no_content }
end
end
end
Note.rb
class Note < ActiveRecord::Base
belongs_to :ticket
attr_accessible :note, :ticket_id
end
Ticket.rb
class Ticket < ActiveRecord::Base
attr_accessible :notes_attributes
accepts_nested_attributes_for :notes
end
It is because note_id is an integer type.
Use nested models:
Refer this for Nested Models
Model:
class Ticket < ActiveRecord::Base
has_many :notes
attr_accessible :note_id, :notes_attributes
accepts_nested_attributes_for :notes
end
View:
<%= form_for(#ticket) do |f| %>
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
What you have is a nested association, with Ticket as the "parent". The association is governed by the link from note_id in the Note model to the id (primary key) of the Ticket. What you're presently doing right now is manually manipulating that numeric association. Rails, knowing that the note_id column is supposed to be an integer, is taking the text you're trying to insert and turning it in to a number (zero in this case). You've probably got a bunch of orphaned rows right now because of this.
Ultimately, in order to accomplish what you're trying to do, your form will need to provide fields for that associated model. One way you can handle this is by using the accepts_nested_attributes_for in your Ticket model. Like so:
class Ticket < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
And in your form, you can easily create a nested form like so:
<%= form_for(#ticket) do |f| %>
<div class="field">
<%= f.fields_for :notes do |f_notes|%>
<%= f_notes.label :note %><br />
<%= f_notes.text_area :note, :size => "101x4", :placeholder => "Please leave notes here."%>
<% end %>
</div>
<% end %>
Edit Almost forgot: Check out this Railscast from Ryan Bates dealing with Nested Attributes
Edit 2 As codeit pointed out, you don't need the attr_accessible :note_id in Ticket. Since you've indicated that a Ticket has many Notes, and that Note belongs to Ticket, the foreign key column will appear in the Note model as ticket_id, which you already have. Having note_id in the ticket model is useless, and also nonsensical since has_many describes a plural relationship (which can't be expressed with a single column).
I'm using Mongoid, awesome_nested_fields gem and rails 3.2.8.
I have the functionality working on the client (adding multiple fields), but when I try to save I get a "undefined method `update_attributes' for nil:NilClass" error.
Here is all the relevant info:
profile.rb
class Profile
include Mongoid::Document
include Mongoid::Timestamps
has_many :skills, :autosave => true
accepts_nested_attributes_for :skills, allow_destroy: true
attr_accessible :skills_attributes
end
skill.rb
class Skill
include Mongoid::Document
belongs_to :profile
field :skill_tag, :type => String
end
View
<%= simple_form_for(#profile) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="items">
<%= f.nested_fields_for :skills do |f| %>
<fieldset class="item">
<%= f.input :skill_tag, :label => 'Skills:' %>
Remove Skill
<%= f.hidden_field :id %>
<%= f.hidden_field :_destroy %>
</fieldset>
<% end %>
</div>
Add Skill
</div>
<div class="form-actions">
<%= f.button :submit, :class => 'btn' %>
</div>
<% end %>
profiles_controller.rb
# PUT /profiles/1
# PUT /profiles/1.json
def update
#profile = Profile.find(params[:id])
respond_to do |format|
if #profile.update_attributes(params[:profile])
format.html { redirect_to #profile, notice: 'Profile was successfully updated.' } # Notice
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
*********** UPDATE ************
I ended up switching to Ryan's nested_form gem and it works like a charm.
Your message directly points to the line of execution where it failed, ergo the line:
if #profile.update_attributes(params[:profile])
The problem is that the #profile instance variable is nil and rails can not find the update_attributes method for a nil object.
You could easly check your server log for the params hash, to see what params[:id] (probably it isn't defined at all) by going in the terminal where you started your app.
Or you can check your development log when being in your app folder:
tail -n 1000 development.log | egrep params
I've been having some difficulty in understanding the source of a problem.
Below is a listing of the model classes. Essentially the goal is to have the ability to add sentences to the end of the story, or to add stories to an existing sentence_block. Right now, I'm only attempting to allow users to add sentences, and automatically create a new sentence_block for the new sentence.
class Story < ActiveRecord::Base
has_many :sentence_blocks, :dependent => :destroy
has_many :sentences, :through => :sentence_blocks
accepts_nested_attributes_for :sentence_blocks
end
class SentenceBlock < ActiveRecord::Base
belongs_to :story
has_many :sentences, :dependent => :destroy
end
class Sentence < ActiveRecord::Base
belongs_to :sentence_block
def story
#sentence_block = SentenceBlock.find(self.sentence_block_id)
Story.find(#sentence_block.story_id)
end
end
The problem is occurring when using the show method of the Story. The Story method is as follows, and the associated show method for a sentence is also included.
Sentence.show
def show
#sentence = Sentence.find(params[:id])
respond_to do |format|
format.html {redirect_to(#sentence.story)}
format.xml { render :xml => #sentence }
end
end
Story.show
def show
#story = Story.find(params[:id])
#sentence_block = #story.sentence_blocks.build
#new_sentence = #sentence_block.sentences.build(params[:sentence])
respond_to do |format|
if #new_sentence.content != nil and #new_sentence.sentence_block_id != nil and #sentence_block.save and #new_sentence.save
flash[:notice] = 'Sentence was successfully added.'
format.html # new.html.erb
format.xml { render :xml => #story }
else
#sentence_block.destroy
format.html
format.xml { render :xml => #story }
end
end
end
I'm getting a "couldn't find Sentence_block without and id" error. So I'm assuming that for some reason the sentence_block isn't getting saved to the database. Can anyone help me with my understanding of the behavior and why I'm getting the error? I'm trying to ensure that every time the view depicts show for a story, an unnecessary sentence_block and sentence isn't created, unless someone submits the form, I'm not really sure how to accomplish this. Any help would be appreciated.
edit, the view:
<p>
<b>Title:</b>
<%=h #story.title %>
<% #story.sentence_blocks.each do |b| %>
<% b.sentences.each do |s| %>
<br />
<%=h s.content %>
<% end %>
<% end %>
</p>
<% form_for #new_sentence do |s| %>
<p>
<%= s.label :sentence %><br />
<%= s.text_field :content %>
</p>
<p>
<%= s.submit "Create" %>
</p>
<% end %>
<%= link_to 'Edit', edit_story_path(#story) %>
<%= link_to 'Back', stories_path %>
ruby-debug will be very helpful:
gem install ruby-debug
script/server --debugger
Add the following after setting the #story in your show method:
def show
#story = Story.find(params[:id])
debugger
#sentence_block = #story.sentence_blocks.build
Then go to /stories/1 or whatever id you're using to test, the page will not finish loading, go to the terminal you're using for your server and you will see an irb prompt. You can run arbitrary code from that prompt and you can step through your code. Very useful.