Rails3 Associations and nested attributes - ruby-on-rails-3

I am having trouble when it comes to saving/creating 2 objects at once and associating them to one another. Currently I am doing it in a 'hackish' sort of way by not using nested forms and just passing the parameters for both objects separately (from the view.) Then I connect them in the controller here is my code:
Models
class Post < ActiveRecord::Base
belongs_to :user
has_one :product
accepts_nested_attributes_for :product, :allow_destroy => true
end
class Product < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
View
<%= form_for(#post) do |f| %>
<div id="post_field">
<%= f.text_area :content %>
</div>
<div id="post_link_previewer" class="clearfix">
<%= fields_for :product do |prod| %>
<%= prod.text_field :name %><br />
<%= prod.text_area :description, :rows => 2 %><br />
<%= prod.text_field :image_url %><br />
<%= prod.text_field :original_url %>
<% end %>
</div>
<div id="submit" class="clearfix">
<%= f.submit "Post" %>
</div>
<% end %>
PostsController
def create
#user = current_user
#post = #user.posts.create(params[:post])
#product = Product.create(params[:product])
#post.product_id = #product.id
respond_to do |format|
if #post.save
format.html { redirect_to(root_path, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
So when a user makes a post, they can attach a 'product' to that post if they want. The current way I am doing it makes a lot of sense. When I looked at nested form tutorials and see them using build methods I start to get a little confused as to what is going on. Can you help me understand the best way of linking these 2 objects upon create? Is it best to use nested form fields? I feel the current way I am doing it isn't as efficient as it should be.

Yes, you should use nested forms. There is a reason as to why they were built. They ease the process of managing associations and creating nested objects in a single go.
The build method builds an object (it calls the .new() method for the object) and then you can use it.
I advise you to start with a simple example of nested forms and play around with it for an hour or two. This way, you'll have a better understanding of what's happening underneath.
I think, in this case, self-learning by playing would help you a lot, instead of someone just telling you why nested forms are better.
To get you started, refer to nested-attributes-in-rails.

Related

Rails 3 - checkbox for create (opposite of _destroy)

I have a Query model with a has_many relationship to OutputFields. In my query controller's new function I build several OutputFields within the query instance. In my form, I want each checkbox to determine whether the object is saved (a check means save this instance of OutputField to the database). How can I do this?
my models:
class Query < ActiveRecord::Base
attr_accessible :description, :name
has_many :output_fields, :dependent => :destroy
accepts_nested_attributes_for :output_fields
end
class OutputField < ActiveRecord::Base
attr_accessible :query_id, :column_name, :table_name
belongs_to :query
end
relevant sections of my queries controller. Structure is another model.
# GET /queries/new
# GET /queries/new.json
def new
#query = Query.new
Structure.columns.each do |column|
#query.output_fields.build( :table_name => Structure.table_name, :column_name => column.name )
end
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #query }
end
end
Finally, my view. Right now I'm linking the checkbox to the destroy attribute, which I think will do the exact opposite of what I want.
<%= form_for(#query) do |f| %>
<%= f.fields_for :output_fields do |builder| %>
<div class="field">
<%= builder.check_box :_destroy %>
<%= builder.label :_destroy, builder.object.column_name %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
If it's not obvious, I'm trying generate a user interface for a simple query builder. This is my first rails app, so any advice is appreciated.
By default the value of the check_box form helper is to set the checked_value to '1' and the unchecked_value to '0'. So to reverse the behaviour of the destroy checkbox, just switch these values around.
<%= builder.check_box :_destroy, {}, '0', '1' %>

How to move database operations from controller to model

I've started to work on old project with new tools. This time - Rails on Ruby. I managed to make some progress, and now i want to improve one element of my code.
Whole project is about bugtracking with full history search of all tracked bugs. Now i'm on stage where user is entering bugs. Every bug belong to table which belongs to projects.
Only problem - now - is autocompletion of table name when i'm using completely new name (with tables that are already present in database it's working just fine, fills table_id in Bug entry).
Part of view responsible for entering (or selecting from existing) table looks like this:
<div class="control-group">
<%= f.label :table_name, :class => 'control-label' %>
<div class="controls">
<%= f.autocomplete_field :table_name, autocomplete_table_name_bugs_path %>
</div>
</div>
Nothing unusual. This one goes to the model (Bug.rb)
class Bug < ActiveRecord::Base
attr_accessible :bugid, :fixdate, :fixnote, :fixstate_id, :milestone, :newtarget, :notes, :oldtarget, :project_id, :bugreason, :reason_id, :regressiondate, :regressionstate_id, :source, :stringid, :table_id
belongs_to :project
belongs_to :table
belongs_to :reason
belongs_to :state
def table_name
table.name if table
end
#def table_name=(name)
# self.table = Table.find_or_create_by_name(name) unless name.blank?
#end
end
No validation for now as you can see. table_name=(name) commented as it's apparently doing nothing in my code.
This is working with bugs controller (bugs_controller.rb)
def create
if params[:bug][:table_id].nil?
if Table.find_or_create_by_name(params[:bug][:table_name])
params[:bug][:table_id] = Table.find_by_name(params[:bug][:table_name]).id
params[:bug].delete :table_name
end
end
#bug = Bug.new(params[:bug])
respond_to do |format|
if #bug.save
format.html { redirect_to bugs_path, notice: 'Bug was successfully created.' }
format.json { render json: #bug, status: :created, location: #bug }
else
format.html { render action: "new" }
format.json { render json: #bug.errors, status: :unprocessable_entity }
end
end
end
I put here only part responsible for saving new bugs, i'll manage to handle update part when i do this part right.
What i want to improve is first part. Now it's responsible not only for changing table_name to table_id but for creation of new table if it doesn't exist. I'm aware that this part should be handled by model, but i've no idea how to do that, could use some help.
Another part, as btw. is my dropdown menu where user can select active project. It's handled by partial:
<% #projects.each do |project| %>
<% if project.id == session[:current_project].to_i %>
<li class="disabled"><%= link_to project.name, '#' %></li>
<% else %>
<li><%= link_to project.name, choose_project_path(project.id) %></li>
<% end %>
<% end %>
But it works fine only when used from projects controller. How - by the book - i can handle this from other controllers? To be exact, i want it working same way in whole project.
For now i'm handling it by snippet in every controller, but i'm pretty sure that RoR gods are not happy with me for that.
before_filter :projects
def projects
#projects = Project.all
end
How it should be done in proper way? :)
So i managed to move logic from controller to model. Also, it's saving all data i need - table_id to bugs table and project_id (stored in session) to tables table when creating new table.
So part of form render is not changed:
<div class="control-group">
<%= f.label :table_name, :class => 'control-label' %>
<div class="controls">
<%= f.autocomplete_field :table_name, autocomplete_table_name_bugs_path %>
</div>
</div>
Model now looks like this:
class Bug < ActiveRecord::Base
attr_accessible :table_name, :bugid, :fixdate, :fixnote, :fixstate_id, :milestone, :newtarget, :notes, :oldtarget, :project_id, :bugreason, :reason_id, :regressiondate, :regressionstate_id, :source, :stringid, :table_id
belongs_to :project
belongs_to :table
belongs_to :reason
belongs_to :state
def table_name
table.name if table
end
def table_name=(name)
self.table = Table.find_or_create_by_name(name, project_id: self.project_id ) unless name.blank?
end
end
So only change is uncommenting table_name= method and adding project_id to creation of new table (and lack of project_id was the reason it didn't work earlier).
Controller looks like this:
def create
#bug = Bug.new(params[:bug])
#bug.project_id = session[:current_project]
respond_to do |format|
if #bug.save
format.html { redirect_to bugs_path, notice: 'Bug was successfully created.' }
format.json { render json: #bug, status: :created, location: #bug }
else
format.html { render action: "new" }
format.json { render json: #bug.errors, status: :unprocessable_entity }
end
end
end
It's working like charm. Still i've question remaining, but will post it as separate one.

Rails 3 Cocoon link_to_add_association Renders Partial Twice

My partial gets rendered twice instead of only once, as expected. Any thoughts?
Here's my Person view
<%= simple_nested_form_for(#person) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :name %>
<h3>Records by year</h3>
<div id='records'>
<%= f.simple_fields_for :records do |record| %>
<%= render 'record_fields', :f => record %>
<% end %>
<div class='links'>
<%= link_to_add_association 'New Record', f, :records, :class => 'btn' %>
</div>
</div>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Models (removed things such as constants and validations):
class Person < ActiveRecord::Base
attr_accessible :name, :records_attributes
has_many :records, :dependent => :destroy
accepts_nested_attributes_for :records, :reject_if => :all_blank, :allow_destroy => true
end
class Record < ActiveRecord::Base
attr_accessible :price, :status, :year, :person_id
belongs_to :person
end
My _record_fields.html.erb partial looks like this:
<div class='nested-fields well'>
<%= f.input :price %>
<%= f.input :year %>
<%= f.input :status, :collection => record_statuses, :include_blank => false %>
<%= link_to_remove_association "Remove", f %>
</div>
An interesting issue is that, if I change where the partials are generated (so 'after' instead of the default 'before' the link_to_add_association link), it generates a partial after the button, but the duplicate is generated before the link_to_add_association link.
The only similar issues reported on here I could find were with caching in production. This is happening in development, and my caching is turned off (by default).
Am I missing something? Can anyone help?
Edit:
Looking at the cocoon JavaScript, it seems the click event is called twice for one click (tested it with $('.add_fields').click(), which triggers $('.add_fields').live('click', function() {...} ) twice. I'm still at a loss as to why this might be happening. Input thoroughly appreciated.
Edit #2:
Controller:
# GET /persons/new
# GET /persons/new.json
def new
#person = Person.new
# #person.records.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #person }
end
end
# GET /persons/1/edit
def edit
#person = Person.find(params[:id])
end
I was having this same issue.
My problem was I requiring the cocoon javascript twice.
Once in my application.js
// app/assets/javascripts/application.js
//= require cocoon
and once in my application layout
/ app/views/layouts/application.html.haml
= javascript_include_tag :cocoon
after removing the include tag from my layout it began working as expected
Had the same issue, found that in my application layout (app/views/layouts/application.html.erb) I had already included application, and in application.js I had included cocoon. When including application.js in my view with cocoon, cocoon would fire twice.
(Same as above, but slightly different as I didn't explicitly specify cocoon in my application.html.erb, I specified application.js which included cocoon)
This happen when from application.js, you require two times cocoon, commonly, when you try change of jQuery to vanilla Javascript

Adding fields to nested forms

So I have a model called City and it has_many :places and it accepts_nested_attributes_for :places. Each Place belongs_to :category. When I render a form for a City I have f.fields_for :places do |place| and I do it like this:
<% f.fields_for :places do |place| %>
<%= render "place_fields", :f => place
<% end %>
My _place_fields.html.erb contains the folowing:
<div class="place_header"><%= f.object.category.name %></div>
<div><%= f.label :name %>: <%=f.text_field :name %> </div>
<div><%= f.text_area :description %> </div>
But the problem apears when I try to add a new place. First of all I want to bring up a simple select form to select a category for the new place, and then render that same partial based on the category_id.
I do that inside the same action:
def add_place
if params[:category_id]
#place = Place.new(:category_id => params[:category_id])
respond_to do |format|
format.html { return nil }
format.js {
#here comes the render
}
end
else
render_message :header => "Choose category", :partial => "category_select", :over => 10
end
end
But if I try to do $("#places_tab").append("<%= escape_javascript(render :partial => "place_fields", :f => #place %>"); it gives an error, wich is expected.
Once again: I need to render the fields for that new Place and just don't know how to do that.
UPDATE
Received some advice on passing the original City Formbuilder to the action and rendering that Place right from that builder, but don't have any idea of how to do that.
The problem is that you are passing an instance of the Place model (#place) as the form builder instead of the form builder itself.

New record not being saved with any values

I started the question differently, about a collection_select, but I found out that is not the problem.
This particular model won't save any data at all. It just ignores the values in the parameters. I can only save new records with NULL values (except for the timestamp fields).
See my comment for my latest try to fix it.
I have generated a few models with the handy scaffold command. Now I have tried to change a textbox to a collection_select for linking the new entity to the correct related one.
Using rails 3.1RC4 (hopefully this is not a bug).
In the _form.html.erb I use the following code:
<div class="field">
<%= f.label :category_id %><br />
<%= f.collection_select(:category_id, Admin::Category.all, :id, :name) %>
</div>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
...all other items...
<div class="actions">
<%= f.submit %>
</div>
After I click the submit button I receive error messages. It says that the name and permalink do not comply to the validation. I don't understand however, because in the logfiles I find this:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"my token is here", "admin_branche"=>{"category_id"=>"3", "name"=>"Verzekeraars", "permalink"=>"verzekeraars", "visible"=>"1"}, "commit"=>"Create Branche"}
To me it seems that the params contain all the needed values.
For the sake of completeness I will post my create method and model below.
So far I have tried switching back and forth between collection_select and f.coll... with no success. The current setup seems most appropriate to me, based on the logs.
I have also googled a lot, but haven't been able to find the answer. Question 2280106 on this site looks the same, but it had to do with attr_accessible which I have commented out in the model (I restarted the server afterwards and retried, just to be sure).
Help is much appreciated!
branche.rb:
class Admin::Branche < ActiveRecord::Base
# attr_accessible :name, :permalink
#relationships
has_many :courses, :as => :parent
belongs_to :category
#validations
validates :name, :presence => true, :length => {:maximum => 255}
validates :permalink, :presence => true, :length => { :within => 4..25 }
end
create action in the controller:
def create
#admin_branch = Admin::Branche.new(params[:admin_branch])
respond_to do |format|
if #admin_branch.save
format.html { redirect_to #admin_branch, notice: 'Branche was successfully created.' }
format.json { render json: #admin_branch, status: :created, location: #admin_branch }
else
format.html { render action: "new" }
format.json { render json: #admin_branch.errors, status: :unprocessable_entity }
end
end
end
In the controller, you're doing this:
#admin_branch = Admin::Branche.new(params[:admin_branch])
You should do this:
#admin_branch = Admin::Branche.new(params[:admin_branche])
If you look at the request parameters, the attributes are under "admin_branche", not "admin_branch".
I think that should solve your problems, if not, please let us know.
If you have problems with the generated inflections, you can completely customize them in the config/initializers/inflections.rb
just add something like this:
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'branch', 'branches'
end