In our app, we have, among others, these three models:
class PackContent < ActiveRecord::Base
belongs_to :product
belongs_to :pack
class Product < ActiveRecord::Base
has_many :pack_contents
has_many :packs, through: :pack_contents
class Pack < ActiveRecord::Base
has_many :pack_contents
has_many :products, through: :pack_contents
accepts_nested_attributes_for :pack_contents
A pack has multiple pack_contents, which are basically a product and some specific productvariants. The join table (pack_contents) looks like this:
I am now trying to display these pack_contents in a usable manner, but keep running into a wall.
I have tried:
.pack-product-rows
- #pack.pack_contents.each do |pc|
= f.simple_fields_for pc do |p|
= render :partial => 'pack_product_row', :locals => { :p => p, :pc => pc }
But then, upon saving, I get an error saying:
ActiveRecord::UnknownAttributeError in Admin::PacksController#update
unknown attribute: pack_content
And the form field name attributes are wrong, they don't account for multiple contents as they don't include an index.
When I try this:
.pack-product-rows
- #pack.pack_contents.each do |pc|
= f.simple_fields_for :pack_contents do |p|
= render :partial => 'pack_product_row', :locals => { :p => p, :pc => pc }
It renders every partial twice, which, I suppose, is to be expected.
If i don't loop over the contents and just use simple_fields_for, I get the output I want, but not how I want it. I can render the fields with p.input_field :id and the likes, but I need access to the pack_contents in order to display my data like this: (input fields meant to be hidden)
Not sure how to proceed.
I'm not sure if I've understood what you're asking but if you just want an access to the current item being automatically iterated via simple_fields_for, then you can access it through the form builder as p.object.
I am assuming that you are trying to build the nested form on the views/packs/show.html.erb where in you have the #pack instance variable initialized to a pack from the database. So on this page you will have a - simple_form_for #pack form inside which you want to nest over the pack_contents.
Now the simple_form_fields method of the simple_form gem works just like the fields_for method in rails. Based on that I am giving you the code below. It should work properly but you may have make a few syntactical changes to make it work.
- simple_form_for #pack do |pack_form|
.pack-product-rows
- #pack.pack_contents.each do |pack_content|
= f.simple_fields_for :pack_contents, pack_content do |pack_content_fields|
= render :partial => 'pack_product_row', :locals => { :pack => #pack, :pack_content => pack_content, :pack_content_fields => pack_content_fields}
Now in the partial pack_product_row you will need to use the three locals passed. You must be already using pack and pack_content but whatever fields for pack_contents you want to display should be written as pack_content_fields.field_name. If your naming convention is correct for all the fields then these fields will be either empty if the pack_content is empty or having the value stored in the database.
Related
I have a set of models like below (the actual setup displayed here is a simplified mockup):
Blog
has_many :posts
Post
has_many :comments
# contributor_id is a part of Post
Contributor
# having fields such as :id, :name, etc.
I iterate through them like this:
Blog.all.includes(:posts => :contributors).each do |blog|
render partial: 'blog_item', :locals => { :blog => blog }
end
Inside 'blog_item' partial view I would like to determine whether a specific Contributor (id: 123, name:"john") is included among the posts. As a general problem it is quite easy, but I want to do this without having an SQL call (causing N+1).
Currently, I am solving this like so:
blog.posts.pluck(:contributor_id).include?(123)
which causes an SQL call (and subsequently an N+1).
I can't do something like this:
blog.posts.include?(:contributor_id)
So, how can this be solved without having that extra SQL call?
I have been trying to solve this for quite some time. Posted the question, read a subsection of an article directly after and solved it in 2 mins.
#contributor = Contributor.find(123)
Blog.all.includes(:posts => :contributors).each do |blog|
render partial: 'blog_item', :locals => { :blog => blog }
end
# Inside blog_item
blog.posts.map(&:contributor).include?(#contributor)
In Rails 3:
I have the following models:
class System
has_many :input_modes # name of the table with the join in it
has_many :imodes, :through => :input_modes, :source => 'mode', :class_name => "Mode"
has_many :output_modes
has_many :omodes, :through => :output_modes, :source => 'mode', :class_name => 'Mode'
end
class InputMode # OutputMode is identical
belongs_to :mode
belongs_to :system
end
class Mode
... fields, i.e. name ...
end
That works nicely and I can assign lists of Modes to imodes and omodes as intended.
What I'd like to do is use accepts_nested_attributes_for or some other such magic in the System model and build a view with a set of checkboxes.
The set of valid Modes for a given System is defined elsewhere. I'm using checkboxes in the _form view to select which of the valid modes is actually set in imodes and omodes . I don't want to create new Modes from this view, just select from a list of pre-defined Modes.
Below is what I'm currently using in my _form view. It generates a list of checkboxes, one for each allowed Mode for the System being edited. If the checkbox is ticked then that Mode is to be included in the imodes list.
<% #allowed_modes.each do |mode| %>
<li>
<%= check_box_tag :imode_ids, mode.id, #system.imodes.include?(modifier), :name => 'imode_ids[]' %>
<%= mode.name %>
</li>
<% end %>
Which passes this into the controller in params:
{ ..., "imode_ids"=>["2", "14"], ... }
In the controller#create I extract and assign the Modes that had their corresponding checkboxes ticked and add them to imodes with the following code:
#system = System.new(params[:system])
# Note the the empty list that makes sure we clear the
# list if none of the checkboxes are ticked
if params.has_key?(:imode_ids)
imodes = Mode.find(params[:imode_ids])
else
imodes = []
end
#system.imodes = imodes
Once again that all works nicely but I'll have to copy that cludgey code into the other methods in the controller and I'd much prefer to use something more magical if possible. I feel like I've passed off the path of nice clean rails code and into the forest of "hacking around" rails; it works but I don't like it. What should I have done?
Update: After thinking about this some more I think this question includes both polymorphism and STI.
Let's say I have the following:
class Vehicle < ActiveRecord::Base
belongs_to :dealership
...
end
class Car < Vehicle
...
end
class Truck < Vehicle
...
end
The Car and Truck models have the same attributes but different logic in the model, so they all just use the Vehicles table.
I'd like to make a generic form that allows someone to create a new vehicle record, and to select whether it is a "Car" or "Truck". On the backend this form should then create a "Car" or "Truck" record, not a generic "Vehicle" record.
Question:
How do you present this option in a Rails form? Specifically, are there methods for listing the child types of an abstract base model, and/or is there a simple way to present these as (for instance) a Select element in a form?
The reason I am looking to create a form that can make a car or a truck opposed to just having two different forms for cars and trucks, is eventually I'd also like these models to be nested in a parent, "Dealership." I'd like to be able to not only create new vehicles and select "car" or "truck" as an option, but to be able to have nested forms where a user could load a "Dealership" and view/edit all the cars and trucks that belong to that dealership in a single form. I know how to set that up using accepts_nested_attributes_for on a simple association like Vehicle belongs_to :dealership, but I'm not sure how to set it up for child models that inherit from Vehicle.
I've had the same problem. Basically you are trying to build a form that accepts has_many association of a base class, but you want to create individual forms (add nested fields) for any of the Decorated / Concrete classes dynamically.
I solved this using the cocoon gem, which provided methods that dynamically add the correct fields to the form. The _form partial looks something like:
# _form.html.haml
= simple_form_for #dealership, :html => { :multipart => true } do |f|
= f.simple_fields_for :vehicles do |vehicle|
= render 'vehicle_fields', :f => vehicle
= link_to_add_association 'Add a Car', f, :vehicles, :wrap_object => Proc.new { |vehicle| vehicle = Car.new }
= link_to_add_association 'Add a Truck', f, :vehicles, :wrap_object => Proc.new { |vehicle| vehicle = Truck.new }
= f.button :submit, :disable_with => 'Please wait ...', :class => "btn btn-primary", :value => 'Save'
And then the _vehicle_fields
# _vehicle_fields.html.haml
- if f.object.type == 'Car'
= render 'car_fields', :f => f
- elsif f.object.type == 'Truck'
= render 'truck_fields', :f => f
car_fields and truck_fields would also feature a link link_to_remove_association provided by cocoon
Take a look at :wrap_object documentation at https://github.com/nathanvda/cocoon , or read my blog post at http://www.powpark.com/blog/programming/2014/05/07/rails_nested_forms_for_single_table_inheritance_associations
I might be wrong, but I understand polymorphic as the other way around. I mean, a model can belong to more than one other model...
Car and Truck is a kind of Vehicle. So, lets say that Vehicle has year and mark attributes.
You are able to create a new Car or Truck, and having the polymorphic defined, you will create your form as usual.
#car = Car.find(id)
#vehicle = #car.vehicles.build(year: "1999", mark: "Beetle")
#vehicle.save
I hope it helps...
I don't think that a simple select element on the form is the solution to your problem. What you are trying to achieve here is called Multiple Table Inheritance and your problem should be the way you must structure your models in order be easily reflected on the database. Because it is a long discussion I will point you to this article where I explain the optimal way of doing this. I hope I helped you in a way :)
PS: To answer your question, no there is no built-in mechanism of doing that kind of thing in the Rails framework.
I have three models:
class Client < ActiveRecord::Base
has_many :balancesheets
has_many :investment_assets, :through => :balancesheets
class Balancesheet < ActiveRecord::Base
belongs_to :client
has_many :investment_assets, :dependent => :destroy
accepts_nested_attributes_for :investment_assets, :allow_destroy => true
class InvestmentAsset < ActiveRecord::Base
belongs_to :balancesheet
belongs_to :client
I have two questions that have to do with the foreign key client_id. First, when I create a new balancesheet, I use collection_select to select the client from the list. I would rather put the new balancesheet link on the client show page and just pass the client id through to the form so I don't have to choose a client from a list or type it in a field. So first, how do I do that?
Second, the investment_asset model is nested in the balancesheet form. Everything works just fine, except the client_id attribute for the investment_asset is blank. Im not sure why because my associations seem to be okay. So the question is, how do I pass this client_id attribute down through the nested form? I can post models or controllers, just let me know.
UPDATE I have since found the answer to my first question here. However, I am still trying to figure out the second part based on that answer, which is, how do I pass this client_id down through a nested form? To show how it worked passing user id when creating a balancesheet, here is the client show page link:
<%= link_to 'New BalanceSheet',new_balancesheet_path(:id => #client.id) %>
In the balancesheet form I have a hidden field:
<%= f.hidden_field :client_id %>
And in the Balancesheet Controller this is the new action:
#balancesheet = Balancesheet.new(:client_id => params[:id])
This works just fine. So for an investment_asset I am using Ryan Bates nested_form gem which has a little bit different syntax for a link. So inside the balancesheet form the link to add a new investment_asset is:
<%= f.link_to_add "Add asset", :investment_assets %>
I can't figure out how to pass the user id like I did on the balancesheet with:
(:id => #client.id)
I can put the same thing in a new action in the investment_asset controller and add a hidden field, but I'm not sure how to pass it in on the link. Thoughts?
This might be a total hack for all I know, but all I know is very limited. What I ended up doing here was user jQuery to take the id from the balancesheet and force it into a hidden field for investment_asset. jQuery:
// get the value from the balancesheet hidden field that contains the user_id value
var balsheetclientid = jQuery('#hiddenid').attr('value');
// insert it into the value attribute in the hidden field with class name .iaclientid
jQuery('.iaclientid').val(balsheetclientid)
Then my hidden field looks like this:
<%= f.hidden_field :client_id, :class => 'iaclientid', :value => '' %>
It works just fine. Hopefully that's a valid way of doing it.
This should be a layup for someone...
I'm trying to change a form field's attribute depending on which controller/model is calling the partial containing the form fields...
The issue (below) is with parent_id... which references one of two columns in a dogs table. It needs to either be kennel_id or master_id depending on which view this partial is being rendered in.
Not comfortable enough, yet, with Ruby/Rails language/syntax/tools to dynamically change this without getting bogged down in if/else statements.
I'm calling a shared partial and passing in a local variable:
= render "dogs/form", :parent => #kennel
or
= render "dogs/form", :parent => #master
In the partial I'd like to:
= form_for ([parent, target.dogs.build]) do |f|
= render "shared/error_messages", :target => parent
.field
= f.label :name
= f.text_field :name
.field
= f.hidden_field :parent_id ### <= PROBLEM
.actions
= f.submit 'Save'
Just thinking out loud:
I don't know if the parent-models have the proper names for it, but you could do something like:
= f.hidden_field "#{parent.class.name.underscore}_id"
But that doesn't look right. So, why not pass it as an argument?
= render "dogs/form", :parent => #master, :foreign_key => :master_id
Or, create aliases on the dog model to handle some sort of dynamic delegation:
class Dog
def parent_id=(parent_id)
case parent.class
when Master then self.master_id = parent_id
when Kennel then self.kennel_id = parent_id
end
end
def parent_id
case parent.class
when Master then self.master_id
when Kennel then self.kennel_id
end
end
end
But that sucks too. Could the relation be polymorphic? Then you can leave out the switching.
class Dog
belongs_to :owner, :polymorphic => true
end
= f.hidden_field :owner_id
Just some ideas. Hopefully one of them makes sense to you...
Wow, my initial answer wasn't even close. I think what you'll want is a polymorphic association: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations This way, the parent can be whatever class you need it to be.