Nested Model Form. Belongs_to class not displaying - ruby-on-rails

I have a Subject Model and a Lesson Model.
I implemented a nested model form.
After subject creation, I led it to a page where it supposedly shows the lessons associated. However, I fail to see the lessons.
I believe the data for lessons did not get saved properly as
When I did a for example lesson.find_by_subject_id('1'), I get 'nil' in return.
I am trying to figure out how polymorphism works on rails and would appreciate it if someone could either point out where I've gone wrong or give me some guidance on how those to pass the values for belong_to classes to be created.
Subject Model
attr_accessible :subjectCode, :lessons_attributes
has_many :lessons, :dependent => :destroy
accepts_nested_attributes_for :lessons, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
Lesson Model
attr_accessible :lessonName, :subject, :subject_id
belongs_to :subject
Subject Controller
def new
3.times {#subject.lessons.build}
end
def create
#subject = Subject.new(params[:subject])
if #subject.save
redirect_to #subject, :notice => "Successfully created subject."
else
render :action => 'new'
end
end
Form
<%= form_for #subject do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :subjectCode %><br />
<%= f.text_field :subjectCode %>
</p>
<%= f.fields_for :lessons do |builder| %>
<p>
<%= builder.label :lessonName %> <br/>
<%= builder.text_area :lessonName, :rows=>3 %>
</p>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
Routes
resources :subjects do resources :lessons end

Your reject_if lambda will always reject the lessons attributes because lessons don't have a content attribute, so you're essentially evaluating nil.blank? which will return true
Perhaps you want to check if the lesson name is blank? Ala :reject_if => lambda { |a| a[:lessonName].blank? }

You don't have a field for content of lesson on form, so content will be blank with every lesson. And you also use:
:reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
This will check if content is blank, the subject will not save the lesson. This is your problem, because you don't have a content field on your form, so content will blank every time you create subject, you used :reject_if so subject will not save its lesson. If you wan user can put content of lesson later, remove :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true and your lesson will be save with associated subject.

Related

Why is updating a form duplicating my form fields and duplicating records with habtm and nested form?

I have a nested form inside a parent form that allows you to add fields inside the parent form. The idea is that a book can have many schools and the school belongs to the book(habtm) relationship. here are my models:
books.rb
accepts_nested_attributes_for :author
accepts_nested_attributes_for :gallery, :allow_destroy => true
accepts_nested_attributes_for :schools, :reject_if => :find_school, :allow_destroy => true
accepts_nested_attributes_for :images, :allow_destroy => true
def find_school(school)
if existing_school = School.find_by_school_name(school['school_name'])
self.schools << existing_school
return true
else
return false
end
end
school.rb
class School < ActiveRecord::Base
has_and_belongs_to_many :books
validates :school_name, :address, presence: true
#validates_uniqueness_of :school_name
end
In my form for the book I have this setup:
<div id="school" class="field">
<h3>Schools reading this book (add the name and full address of the school)</h3>
<%= f.simple_fields_for :schools, :wrapper => 'inline' do |builder| %>
<%= render 'school_fields', :f => builder %>
<%= link_to_add_association 'add school', f, :schools, :render_options => {:wrapper => 'inline' }, :class => 'fa fa-plus' %>
<% end %>
</div>
In here it should render the fields but I get this
The issue i have is that in my book.rb model the find_school method keeps returning multiple values of the same books when I update the book. The idea is that the find_school method basically stops duplicate schools being created as records and just assigns them as a relationship to the book instead. What I am finding is that this self.schools << existing_school on update just keeps duplicating the fields in the edit form and adds the fields as entries on the item when I check in the params output.
Can anyone help with this

Rails: nested_form_for <column> can't be blank

I am trying to use the nested_form gem to create a dropdown that will show a list of chains and their locations. When the user clicks the submit button, the nested_form should create a new entry in a joiner table called users_locations. However, the form spits out an error as follows:
User locations user can't be blank
In other words, its complaining that the user_id is NULL and therefore it cannot create an entry in the table (because user_id is a required field in the user_locations table).
_form.html.erb (In the User view folder)
<div class="field" id="location_toggle">
<%= f.label "Location:", :class => "form_labels", required: true %>
<%= f.fields_for :user_locations do |location| %>
<%= location.grouped_collection_select :location_id, Chain.all, :locations, :name, :id, :name, include_blank: false %>
<% end %>
<%= f.link_to_add "Add a Location", :user_locations, :class => "btn btn-primary btn-small" %>
<br/>
</div>
user.rb (model)
belongs_to :chain
has_many :user_locations
has_many :locations, :through => :user_locations
...
accepts_nested_attributes_for :user_locations, :reject_if => :all_blank, :allow_destroy => true
validates_associated :user_locations, :message => ": you have duplicate entries."
...
attr_accessible :first_name, :last_name, ... , :user_locations_attributes
users_controller.rb
def create
#user = User.new(params[:user])
#user.save ? (redirect_to users_path, notice: 'User was successfully created.') : (render action: "new")
end
Log shows:
SELECT 1 AS one FROM "user_locations" WHERE ("user_locations"."user_id" IS NULL AND "user_locations"."location_id" = 1)
I'm assuming you're user_location validates :user_id, presence: true or something, right?
I had a look at this recently in response to this question, it seems that, when creating nested models, validations for all objects to be created are run before any of them are saved, and thus even though your model will get have user_id set when it is saved, it cannot have it when validated.
To get around this you may want to disable the validation on creation, for example with:
# user_locations.rb
validates :user_id, presence: true, on: :update
It looks like you're blending simple_form with the default Rails form helpers and getting some odd behavior. I haven't worked with simple_form, but my guess is that you can fix your problem by changing f.fields_for to f.simple_fields_for.
Here's an example that may help.

Rails model - how to differentiate different types

I am currently working on a nested model form.
I have a subject model.
This subject model has lessons of 3 different types - tutorial, lecture and laboratory.
I am able to get the nested form working with https://github.com/ryanb/nested_form.
But I want to fix it such that in the form only 3 forms for the child(lesson model) will be produced and that their first field (lesson_type field) will be automatically filled in and fixed.
I am not too sure on how to model such a situation on Rails.
These are the codes I have so far.
Any advice on what I could try out or point out the mistakes I have made would be appreciated.
This is the form.
Right now I could get the form to show up three times on my controller but I am not sure how I could generate different values for the fields. They are all showing lecture as of now.
<%= nested_form_for(#subject, :remote=>true) do |f| %>
<div class="field">
<%= f.label :subject_code %><br />
<%= f.text_field :subject_code %>
</div>
<%= f.fields_for :lessons do |lesson_form| %>
<%= lesson_form.label :lesson_type %><br/>
<%= lesson_form.text_field :lesson_type, :value=> "lecture"%><br/>
<%= lesson_form.label :name %><br/>
<%= lesson_form.text_field :name %><br/>
<%= lesson_form.fields_for :lesson_groups do |lesson_group_form| %>
<%= lesson_group_form.label :group_index %><br/>
<%= lesson_group_form.text_field :group_index %>
<%= lesson_group_form.link_to_remove "Remove this task" %>
<% end %>
<p><%= lesson_form.link_to_add "Add a lesson_group",:lesson_groups,:id=>"open-lesson"%></p>
<% end %>
<% end %>
This is the controller. The creation will happen on the index page.
def index
#subjects = Subject.all
#subject = Subject.new
lecture = #subject.lessons.build
lecture.lesson_groups.build
lecture.destroy
tutorial = #subject.lessons.build
tutorial.lesson_groups.build
tutorial.destroy
laboratory = #subject.lessons.build
laboratory.lesson_groups.build
laboratory.destroy
respond_to do |format|
format.html # index.html.erb
format.json { render json: #subjects }
format.js
end
end
The subject model
class Subject < ActiveRecord::Base
attr_accessible :subject_code, :lessons_attributes
has_many :lessons, :dependent => :destroy
accepts_nested_attributes_for :lessons, :allow_destroy => :true, :reject_if => lambda { |a| a[:lesson_type].blank? }
end
And the lesson model
class Lesson < ActiveRecord::Base
belongs_to :subject
attr_accessible :lesson_type, :name, :subject, :lesson_groups_attributes
has_many :lesson_groups, :dependent => :destroy
accepts_nested_attributes_for :lesson_groups, :allow_destroy => true
end
Okay, I am not sure if this is to the Rails convention but I got it working according to what I want. Added the following lines in the subject model: Basically assigning the lesson type field in the model.
lecture = #subject.lessons.build
lecture.lesson_type = "lecture"
lecture.lesson_groups.build
lecture.destroy
tutorial = #subject.lessons.build
tutorial.lesson_type = "tutorial"
tutorial.lesson_groups.build
tutorial.destroy
laboratory = #subject.lessons.build
laboratory.lesson_type = "laboratory"
laboratory.lesson_groups.build
laboratory.destroy
And to make it such that they can't change the lesson type I made it read only
<%= lesson_form.text_field :lesson_type, :readonly=>true%><br/>

Ruby on Rails: How to validate nested attributes on certain condition?

I have these models:
class Organisation < ActiveRecord::Base
has_many :people
has_one :address, :as => :addressable,
:dependent => :destroy
accepts_nested_attributes_for :address, :allow_destroy => true
end
class Person < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :organisation_id, :address_attributes
belongs_to :user
belongs_to :organisation
has_one :address, :as => :addressable,
:dependent => :destroy
accepts_nested_attributes_for :address, :allow_destroy => true
# These two methods seem to have no effect at all!
validates_presence_of :organisation, :unless => "address.present?"
validates_associated :address, :unless => "organisation.present?"
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
validates_presence_of :line1, :line2, :city, :zip
end
...and these views:
_fields.html.erb:
<%= render 'shared/error_messages', :object => f.object %>
<fieldset>
<div class="left">
<%= f.label :first_name %><br/>
<%= f.text_field :first_name %>
</div>
<div>
<%= f.label :last_name %><br/>
<%= f.text_field :last_name %>
</div>
<div>
<%= f.label :email %><br/>
<%= f.text_field :email %>
</div>
<div>
<%= f.label :organisation_id %><br/>
<%= f.select(:organisation_id, current_user.organisation_names, {:include_blank => "--- None ---"}, :id => 'organisation_select') %>
</div>
</fieldset>
<%= f.fields_for :address do |address| %>
<%= render 'shared/address', :f => address %>
<% end %>
_address.html.erb:
<fieldset id="address_fields">
<div>
<%= f.label :line1 %>
<%= f.text_field :line1 %>
</div>
<div>
<%= f.label :line2 %>
<%= f.text_field :line2 %>
</div>
<div>
<%= f.label :zip %>
<%= f.text_field :zip %>
</div>
<div>
<%= f.label :city %>
<%= f.text_field :city %>
</div>
</fieldset>
people_controller.rb:
def new
puts params.inspect
#person = Person.new(:organisation_id => params[:organisation_id])
#person.build_address
#title = "New person"
end
{"action"=>"new", "controller"=>"people"}
def edit
puts params.inspect
#title = #person.name
end
{"action"=>"edit", "id"=>"69", "controller"=>"people"}
def create
puts params.inspect
if params[:organisation_id]
#person = current_user.organisations.build_person(params[:person])
else
#person = current_user.people.build(params[:person])
end
if #person.save
flash[:success] = "Person created."
redirect_to people_path
else
render :action => "new"
end
end
{"commit"=>"Create", "action"=>"create", "person"=>{"last_name"=>"Doe", "organisation_id"=>"9", "email"=>"john.doe#email.com", "first_name"=>"John", "address_attributes"=>{"city"=>"Chicago", "zip"=>"12345", "line2"=>"Apt 1", "line1"=>"1 Main Street"}}, "authenticity_token"=>"Jp3XVLbA3X1SOigPezYFfEol0FGjcMHRTy6jQeM1OuI=", "controller"=>"people", "utf8"=>"✓"}
Inside my Person model I need to make sure that only if a person's organisation_id is blank, that person's address fields have to be present.
I tried something like this:
validates :address, :presence => true, :if => "organisation_id.blank?"
But it's not working.
How can this be done?
Thanks for any help.
First of all, I want to be sure that you mean blank? rather than present?. Typically, I see this:
validate :address, :presence_of => true, :if => 'organisation.present?'
Meaning, you only want to validate address if organisation is also present.
Regarding, :accepts_nested_attributes_for, are you using this feature by passing in nested form attributes, or some such thing? I just want to make sure you absolutely need to use this functionality. If you are not actually dealing with nested form attributes, you can implement cascading validation using:
validates_associated :address
If you do need to use :accepts_nested_attributes, be sure to check out the :reject_if parameter. Basically, you can reject adding an attribute (and it's descendants) altogether if certain conditions apply:
accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :no_organisation
def no_organisation(attributes)
attributes[:organisation_id].blank?
end
Now, if none of the above apply, let's take a look at your syntax:
It should work, :if/:unless take symbols, strings and procs. You don't need to point to the foreign_key, but can simplify by pointing to:
:if => "organisation.blank?"
You have other validations in the Address model, correct? Is Address being validated when you don't want it to? Or is Address not being validated? I can help you test it out in the console if you can give me some additional details.
To make things easier for myself re: mass-assignment, I changed the rails config: config.active_record.whitelist_attributes = false
I created a gist for you to follow along
I have a sample project as well. Let me know if you are interested.
Basic points:
Added the following to Person to ensure that either Org or Address are valid:
validates_presence_of :organisation, :unless => "address.present?"
validates_associated :address, :unless => "organisation.present?"
Added validation to Address to trigger errors when Org is not present:
validates_presence_of :line1, :line2, :city, :zip
I was able to produce the requirements you are seeking. Please look at the gist I created where I have a full console test plan.
I added a controller file to the previous gist.
Overview:
All you should need to create the person is:
#person = current_user.people.build(params[:person])
:organisation_id will always be found off of the :person param node, like so:
params[:person][:organisation_id]
So you're if will never be true.
I updated the gist with the necessary changes to the controller, the model and the form.
Overview:
You need to cleanup your controller. You are using accepts_nested_attribute, so in the :create, you only care about params[:person]. Additionally, in the render :new, you need to setup any instance variables that the partial will use. This does NOT go back through the :new action. The :new and :edit actions also need to be simplified.
Your Person model needs to use the :reject_if argument because the Address fields are coming back to the :create action as :address_attributes => {:line1 => '', :line2 => '', etc}. you only want to create the association if any have values. Then your validates_presence_of for :organisation will work just fine.
Your form needs to pass the organisation id to the controller, rather than the organisation names
It's all in the gist
Should be the final gist.
Overview:
Add the following to your edit action right after building the #person:
#person.build_address if #person.address.nil?
This ensure that you have the address inputs, even if the #person.address does not exist. It doesn't exist, because of the :reject_if condition on accepts_nested_attributes
I DRYed up the :reject_if as follows. It's a little hacky, but has some utility:
accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :attributes_blank?
def attributes_blank?(attrs)
attrs.except('id').values.all?(&:blank?)
end
a. attrs -> the result of params[:person][:address]
b. .except('id') -> return all key-values except for 'id'
c. .values -> return all values from a hash as an array
d. .all? -> do all elements in the array satisfy the following check
e. &:blank -> ruby shorthand for a block, like this: all?{ |v| v.blank? }
Are you sure you didn't mean:
validates :address, :presence => true, :if => organisation_id.nil?
A more simple approach might be to add a custom validator. It's super easy, and you don't have to stumble on syntax or try to figure out why Rails' magic isn't working.
Inside my Person model I need to make sure that only if a person's organisation_id is blank, that person's address fields have to be present.
class Person < ActiveRecord::Base
...
validate :address_if_organisation_id_is_present
private
def address_if_organisation_id_is_present
return true unless organisation_id
errors.add(:address, "cannot be blank") unless address
end
end
Adding to a model's errors will prevent it from saving. Note: you may wish to use address.blank? or address.empty? as discussed in other answers, but you can define this for the behavior you'd like.

Marking multi-level nested forms as "dirty" in Rails

I have a three-level multi-nested form in Rails. The setup is like this: Projects have many Milestones, and Milestones have many Notes. The goal is to have everything editable within the page with JavaScript, where we can add multiple new Milestones to a Project within the page, and add new Notes to new and existing Milestones.
Everything works as expected, except that when I add new notes to an existing Milestone (new Milestones work fine when adding notes to them), the new notes won't save unless I edit any of the fields that actually belong to the Milestone to mark the form "dirty"/edited.
Is there a way to flag the Milestone so that the new Notes that have been added will save?
Edit: sorry, it's hard to paste in all of the code because there's so many parts, but here goes:
Models
class Project < ActiveRecord::Base
has_many :notes, :dependent => :destroy
has_many :milestones, :dependent => :destroy
accepts_nested_attributes_for :milestones, :allow_destroy => true
accepts_nested_attributes_for :notes, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end
class Milestone < ActiveRecord::Base
belongs_to :project
has_many :notes, :dependent => :destroy
accepts_nested_attributes_for :notes, :allow_destroy => true, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end
class Note < ActiveRecord::Base
belongs_to :milestone
belongs_to :project
scope :newest, lambda { |*args| order('created_at DESC').limit(*args.first || 3) }
end
I'm using an jQuery-based, unobtrusive version of Ryan Bates' combo helper/JS code to get this done.
Application Helper
def add_fields_for_association(f, association, partial)
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(partial, :f => builder)
end
end
I render the form for the association in a hidden div, and then use the following JavaScript to find it and add it as needed.
JavaScript
function addFields(link, association, content, func) {
var newID = new Date().getTime();
var regexp = new RegExp("new_" + association, "g");
var form = content.replace(regexp, newID);
var link = $(link).parent().next().before(form).prev();
if (func) {
func.call();
}
return link;
}
I'm guessing the only other relevant piece of code that I can think of would be the create method in the NotesController:
def create
respond_with(#note = #owner.notes.create(params[:note])) do |format|
format.js { render :json => #owner.notes.newest(3).all.to_json }
format.html { redirect_to((#milestone ? [#project, #milestone, #note] : [#project, #note]), :notice => 'Note was successfully created.') }
end
end
The #owner ivar is created in the following before filter:
def load_milestone
#milestone = #project.milestones.find(params[:milestone_id]) if params[:milestone_id]
end
def determine_owner
#owner = load_milestone || #project
end
Thing is, all this seems to work fine, except when I'm adding new notes to existing milestones. The milestone has to be "touched" in order for new notes to save, or else Rails won't pay attention.
This is bug #4242 in Rails 2.3.5 and it has been fixed in Rails 2.3.8.
i think your models are wrong. the notes have no direct relationship to project. they are through milestones.
try these
class Project < ActiveRecord::Base
has_many :milestones, :dependent => :destroy
has_many :notes, :through => :milestones
accepts_nested_attr ibutes_for :milestones, :allow_destroy => true
end
class Milestone < ActiveRecord::Base
belongs_to :project
has_many :notes, :dependent => :destroy
accepts_nested_attributes_for :notes, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end
class Note < ActiveRecord::Base
belongs_to :milestone
end
Update: here is the code that worked for me based on the new info:
## project controller
# PUT /projects/1
def update
#project = Project.find(params[:id])
if #project.update_attributes(params[:project])
redirect_to(#project)
else
render :action => "edit"
end
end
# GET /projects/1/edit
def edit
#project = Project.find(params[:id])
#project.milestones.build
for m in #project.milestones
m.notes.build
end
#project.notes.build
end
## edit.html.erb
<% form_for(#project) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<% f.fields_for :notes do |n| %>
<p>
<div>
<%= n.label :content, 'Project Notes:' %>
<%= n.text_area :content, :rows => 3 %>
</div>
</p>
<% end %>
<% f.fields_for :milestones do |m| %>
<p>
<div>
<%= m.label :name, 'Milestone:' %>
<%= m.text_field :name %>
</div>
</p>
<% m.fields_for :notes do |n| %>
<p>
<div>
<%= n.label :content, 'Milestone Notes:' %>
<%= n.text_area :content, :rows => 3 %>
</div>
</p>
<% end %>
<% end %>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>

Resources