Rails 3 Nested Form not being created - ruby

The models I'm working with look like this:
class ComplexAssertion < ActiveRecord::Base
has_many :expression_groups
has_many :expressions, :through => :expression_group
accepts_nested_attributes_for :expression_groups, :allow_destroy=>true
end
class ExpressionGroup < ActiveRecord::Base
belongs_to :complex_assertion
has_many :expressions
accepts_nested_attributes_for :expressions, :allow_destroy=>true
end
class Expression < ActiveRecord::Base
belongs_to :expression_group
end
My form looks like the following:
<%= form_for(#complex_assertion) do |f| %>
<div id="mainAssertionGroup" style="border:1px; border-style:solid; width:1000px; padding:5px">
<div class="field">
<%= f.label :title %>: <%= f.text_field :title, :size=>'10' %>
<%= f.label :description %>: <%= f.text_field :description, :size=>'25' %>
<%= f.label :scope %>: <%= f.text_field :scope, :size=>'1' %>
Test
Category: <%= collection_select(:complex_assertion, :assertion_category_id, AssertionCategory.all, :id, :name, {:include_blank=>"UNCATEGORIZED"}) %>
</div>
<div id="initialGroup" style="border:1px; margin-left:10px; margin-top:10px; border-style:solid; width:850px;">
<div class="childGroup1" style="padding:5px;">
<%= f.fields_for :expression_groups do |eg| %>
<%= eg.fields_for :expressions do |e| %>
Type: <%= e.collection_select :assertion_type_id, AssertionType.all, :id, :name %>
Attribute: <%= e.collection_select :attribute_name, Attribute.find_by_sql("select distinct a.name from attributes a "), :name, :name %>
<%= e.label :operator_type_id %>
: <%= e.collection_select :operator_type_id, OperatorType.all, :id, :value %>
Value: <%= e.text_field :value, :size=>'1' %>
<% end %>
<div id="innerOperator">
<%= eg.collection_select :logical_operator_type_id, LogicalOperatorType.all, :id, :value %>
</div>
<% end %>
</div>
</div>
</div>
<div id="createComplex" align="center">
<%= f.submit :value=>'Submit' %>
</div>
<% end %>
And my controller looks like:
def new_complex_assertion
#complex_assertion = ComplexAssertion.new
end
When I load the page, I only the ComplexAssertion portion of the form and get nothing back for the ExpressionGroups or the Expressions. It's as if there isn't anything available. But, if you see my controller, I did a ComplexAssertion.new which I though would create the dependent objects automagically; I assume I'm incorrect?
I'm debugging through RubyMine and when I evaluate the ComplexAssertion.new, I only see 5 attributes, the five that are defined for only that object, none of the relational objects. What am I doing incorrectly?
EDIT
Looks like if I do the following:
#complex_assertion = ComplexAssertion.new
#complex_assertion.expression_groups.build
#complex_assertion.expressions.build
And change my form to use:
<%= f.fields_for :expressions do |e| %>
instead of eg.fields_for, it shows the forms.
This DOES NOT give me the correct nesting. I thought I should be able to do:
#complex_assertion.expression_groups.expressions.build
but it tells me that expressions is an undefined method.

Yes, you have to explicitly instantiate the associated objects. It is not done for you.
#complex_assertion.expression_groups.expressions.build
Will not work because expression_groups is an array and not an individual expression group. So, after you create the expressions_groups do the following:
#complex_assertion.expressions_groups.each do |group|
group.expressions.build
end
Also, you could replace the 2nd line with the following as well to create multiple expressions
2.times do { group.expressions.build }
As for using fields_for with nested models, make your code in the form look like this:
<%= f.fields_for :expression_groups, #complex_assertions.expression groups do |eg| %>
<%= eg.fields_for :expressions, eg.object.expressions do |e| %>
I will try to explain what is going on. The :expressions_groups is telling fields_for what class of object it is going to render fields for, and the second part I added is telling fields_for where to find the object(s) to render fields for. If we are passing in an array, which we are in this case, it will automatically iterate over the array. On each iteration, it puts the current model object we are working with into a variable called object which is stored in the form builder instance returned by fields_for. So we use this to tell the second fields_for where to find the expression model objects it needed. This means eg.object points to an expression_group model object.
I hope this helps and makes sense. Also, I have not tested anything and am only pointing out what looks out of place.

Related

Inserting multiple rows in Rails for single form

There are three fields in this form:
employee
project
project
The field project comes up twice and so I want two records created in this case. If I put in the values:
employee: John Doe
project: Project_1
project: Project_2
I would like two records in the model:
employee: John Doe; project: Project_1
employee: John Doe; project: Project_2
This is the view:
<%= simple_form_for(#source) do |f| %>
<div class="form-group">
<%= f.label :employee %>
<%= f.text_field :employee, class: "form-control" %>
</div>
<div class="form-group">
<%= f.input :project, class: "form-control" %>
<%= f.input :project, class: "form-control" %>
</div>
<% end %>
Here is my code for the application controller:
def create
#source = Source.new(source_params)
if #source.save
redirect_to #source, notice: 'Source was successfully created.'
else
render action: 'new'
end
end
Any help will be greatly appreciated.
In your controller, you should build the model along with two projects, then use the form helper fields_for, which will render both project fields
your_controller.rb
class YourController
def your_action_new
#object = YourModel.new
2.times{ #object.projects.build }
end
end
For the view, I don't really know how simple_form behaves, but basically
views/your_views/new.html.erb
<!-- blabla -->
<%= f.fields_for :project do |project_f| %>
<div class="project">
<%= project_f.text_field(:name) %>
<%= project_f.text_field(:description) %>
...
</div>
<% end %>
ALso don't forget in to accept nested attributes
class YourModel
has_many :projects, dependent: :destroy
accept_nested_attributes_for :projects
end
class YourController
def your_model_params
params.require(:your_model).permit(blabla, projects_attributes: [:id, :name, :blabla, ...])
end
end
end

Nested attributes that do not belong to the current object using has_many :through

My end goal is to be able to add costumes to an agreement in the agreement view, regarless of whether or not they exist in the Costume database yet. My main difficulty is that a costume does not belong to an agreement, they exist independently but can be added to an agreement. If a new costume is added that isn't in the Costume database, it will add it to the database. Is there a way to do this? I can't find a tutorial about this anywhere. If I could get the controller from this post, I think that is all I need. I just need to create one costume every time the form is displayed. Thanks so much.
My models are as follows:
# app/models/agreement.rb
class Agreement < ActiveRecord::Base
has_and_belongs_to_many :costumes, join_table: :agreement_costumes
accepts_nested_attributes_for :costumes, :reject_if => :all_blank
end
# app/models/costume.rb
class Costume < ActiveRecord::Base
has_and_belongs_to_many :agreements, join_table: :agreement_costumes
end
# app/models/agreement_costume.rb
class AgreementCostume < ActiveRecord::Base
belongs_to :agreement
belongs_to :costume
end
My agreement controller is as follows:
class AgreementsController < ApplicationController
before_action :set_agreement, only: [:show, :edit, :update, :destroy]
# Some methods ommitted
# GET /agreements/new
def new
#agreement = Agreement.new
#agreement.costumes.build
#costumes = Costume.all
end
private
# Use callbacks to share common setup or constraints between actions.
def set_agreement
#agreement = Agreement.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def agreement_params
params.require(:agreement).permit(:name, :phone, :email, :mailbox, :wesid, :title, :start, :end, :due, :financer, :employee, :costumes_attributes => [:cid, :description, :wd, :back, :photo])
end
:end
And finally, the agreement view
<%= form_for(#agreement) do |f| %>
<% if #agreement.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#agreement.errors.count, "error") %> prohibited this agreement from being saved:</h2>
<ul>
<% #agreement.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<!-- Agreement fields omitted -->
<%= f.fields_for :costumes do |c| %>
<div class="field">
<%= c.label :cid %><br>
<%= c.number_field :cid %>
</div>
<div class="field">
<%= c.label :description %><br>
<%= c.text_field :description %>
</div>
<!-- etc with costume fields -->
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
You need accepts_nested_attributes_for in your Agreement controller if you want to create new costumes there.
class Agreement < ActiveRecord::Base
has_many :agreement_costumes
has_many :costumes, through: :agreement_costumes
accepts_nested_attributes_for :costumes, :reject_if => :all_blank, :allow_destroy => :false,
end
and then in the agreements#new action in your Agreements controller you build a costume entry
def new
#agreement = Agreement.new
#agreement.costumes.build
#costumes = Costumes.all
end
#agreement.costumes.build creates a blank instance of a costume, related to this agreement. You then access the params of that costume in the form using :costumes Don't forget to whitelist your nested params in your Agreements controller:
def agreement_params
params.require(:agreement).permit(:name, :phone, :email, :mailbox, :wesid,
:title, :start, :end, :due, :financer, :employee, costumes_attributes[:name, :price, :id])
end
Now your form has to have a place to choose existing costumes from a list and/or add a new one.
<%= f.label :costumes, "Costumes" %>
<%= f.collection_select :costumes, :agreement_id, :id, :name, price, {}, {multiple: true} %>
<strong>Add a new costume</strong>
<%= f.fields_for :costumes do |c|
<%= c.label :name %>
<%= c.text_field :name %>
<br>
<%= c.label :price %>
<%= c.number_field :price %>
<br>
<% end %>
This should get you most of the way there. I've had to guess at some of your code, so this isn't going to be a cut and paste answer. You'll need to build what you can off of this and probably do a little more Googling here and there. If you wanted to have a popup form to add a costume to the list on the fly and then be able to choose it in the collection_select, you would have to turn on Turbo_links in your app, create a Javascript popup form. Then use AJAX to submit the form, save the costume to the database, run another .js.erb file that would then update the collection_select text list using a reload of just that list via your Javascript. It's actually probably easier than having a new costume form nested in this form.

How do I add form errors for a model that belongs to another model, rails 3.1

I'm following along with the Rails Guides - Getting Started tutorial. It makes a basic Post model, and a Comment model that belongs to Post.
I have added a simple validation to the Comment model, and it works, but I can't figure out how to get form errors to display if I fill it out wrong.
Here is my comment.rb model
class Comment < ActiveRecord::Base
validates :body, presence: true
belongs_to :post
end
Here is the original form for adding a comment, it's in posts/show.html.erb
<h2>Add a comment:</h2>
<%= form_for([#post, #post.comments.build]) do |f| %>
<div class="field">
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And the original create action in comments_controller.rb
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment])
redirect_to post_path(#post)
end
end
I've tried quite a few things, but it all feels like fumbling around in the dark. Can someone point me in the right direction please?
Take a look at the dynamic_form gem - this used to be part of Rail itself but was extracted a while back. With it, you can display errors inline like this:
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
<%= f.error_message_on :commenter %>

Rails Save File Checkbox

How can I make it so that my info only gets stored if the checkbox is checked. Here is what I have so far:
<% #extra.each do |extra| %>
<%= f.fields_for :purchaseds do |builder| %>
<div class="label-field">
<%= builder.label :name, extra.name %>
<p><%= extra.description %></p>
</div>
<div class="text-field">
$<%= extra.price %>
<%= builder.check_box :purchased %>
</div>
#I WOULD LIKE THIS TO ONLY GET SAVED IF THE CHECK BOX FOR PURCHASED IS CHECKED
<%= builder.hidden_field :name, :value => extra.name %>
<%= builder.hidden_field :description, :value => extra.description %>
<%= builder.hidden_field :price, :value => extra.price %>
<% end %>
<% end %>
My client asked to be able to add extra services himself, and then users could be able to choose if they want to purchase them as accessories to their order. So what I did was I made a table called Extra (for extra services) and another table called Purchased. Purchased belongs to Order and is a nested attribute.
In your purchaseds model add validations:
validates_presence_of :name, :description, :price, :if => :purchased
Update
Add :reject_if option to your purchaseds parent model in
accepts_nested_attributes_for :purchaseds, :reject_if => {|attrs| !attrs[:purchased]}

How to obtain an array of subclasses in Rails

I have a model object which subclasses ActiveRecord. Additionally, using STI, I have defined subclasses of this object, which define different types and behaviors. The structure looks something like this:
class AppModule < ActiveRecord::Base
belongs_to :app
end
class AppModuleList < AppModule
end
class AppModuleSearch < AppModule
end
class AppModuleThumbs < AppModule
end
Now, in a view where the user has the option to create new AppModules, I would like them to select from a dropdown menu. However I have not been able to get a list of subclasses of AppModule using the subclasses() method:
<% form_for(#app_module) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :type %><br />
<%= f.select(:type, options_from_collection_for_select(#app_module.subclasses().map{ |c| c.to_s }.sort)) %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
I get:
NoMethodError: undefined method `subclasses' for #<AppModule:0x1036b76d8>
I'd appreciate any help. Thanks a lot!
It looks as though subclasses and the like is a recent addition (the method exists on various classes at various points in time, but kept getting shuffled around and removed; that link seems to be the earliest point that the method stuck around). If upgrading to the most recent version of RoR isn't an option, you can write your own subclasses and populate it using Class#inherited (which is what RoR's descendents_tracker does).
AppModule.descendants.map &:name is what you're looking for. As in:
<% form_for(#app_module) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :type %><br />
<%= f.select(:type, options_from_collection_for_select(AppModule.descendants.map(&:name).sort)) %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>

Resources