Dropdown Filter for Polymorphic association nested_attribute - drop-down-menu

I'm building an application in which the user can create a Company and add Investments made to that company. The investments can come from two sources user's Funds or companies' Coinvestors. Funds are big deal in the application as the user can do a bunch of stuff in them. The Coinvestors are not that important, but I want to have control over few aspects of so I created a Model just for them. For that I created a Polymorphic association for which I gave the [terrible] name of Investables. I'm running Rails 3.2.15 and Ruby 2.0.0. Models are below:
class Company < ActiveRecord::Base
has_many :investments
accepts_nested_attributes_for :investments
end
class Investment < ActiveRecord::Base
belongs_to :fund, :class_name => "Fund", :foreign_key => 'investable_id'
belongs_to :company, inverse_of: :investments
belongs_to :coinvestor, :class_name => "Coinvestor", :foreign_key => 'investable_id'
end
class Fund < ActiveRecord::Base
has_many :investments, :as => :investable, :dependent => :destroy
end
class Coinvestor < ActiveRecord::Base
has_many :investments, :as => :investable, :dependent => :destroy
end
When editing the company I want to be able to add investments and I want to dynamically add form lines for each new investment. I was able to achieve that following the awesome 165-Edit Multiple Revised.
To make it more complex, I also want to add a Dropdown for choosing the Polymorphic Type so it filters the next Dropdown to show only the names of either the Funds or the Coinvestors.
For that I mostly adapted code from Railscast 88-Dynamic Select Menus(Thanks Ryan!!)
/views/company/edit.html.erb
<%= form_for(#company) do |f| %>
...
<%= f.fields_for :investments do |builder| %>
<%= render 'investment_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add Investment", f, :investments, 'table' %>
/views/company/_investment_fields.html.erb
<%= f.select :investable_type , [ "Fund", "Coinvestor" ], {prompt: "Investor Type"} %>
<%= f.grouped_collection_select( :investable_id, investables_to_collection, :investables, :name, :id, :name, {prompt: "Investor"} ) %>
The "investable_to_collection" is a helper I built to aggregate objects from both Funds and Coinvestors models.
module CompaniesHelper
InvestableCollection = Struct.new(:name, :investables)
CollectionItem = Struct.new(:name, :id)
def investables_to_collection
a = Array.new
a << InvestableCollection.new('Fund', Fund.all.map { |item| CollectionItem.new(item.name, item.id )})
a << InvestableCollection.new('Coinvestor', Coinvestor.all.map { |item| CollectionItem.new(item.name, item.id )})
a
end
end
I didn't add any JavaScript yet to filter the dropdown which will be another challenge. But I've got my beautiful view to show data I've already got in the DB. But the dropdown that should show the Fund's or Coinvestor's name is mixing up things: it will show the name of Coinvestor with ID == 1 even if the investment was made by a Fund.
I thought of making one of the models to have a custom ID such as f1, f2, f3 ... instead of 1, 2, 3... so the system wouldn't mix them. But it seems that it would generate other big compatibility issues.
Do you guys have any other idea?

I wouldn't alter the id column (i.e. I wouldn't change it from an auto-incrementing integer), but because you're mixing two lists in the DB into a single list in the UI you will need some way to note which table each item in the UI came from. For that, sure, use whatever ID scheme you want and then use that differentiating ID as the way to lookup the relevant item.
Also, if you're not going to use the auto-incrementing id columns in the UI (for links or whatever), then you could also just remove them and replace them with your custom identification scheme. There's no reason, for example, that you couldn't assign them all a random 8-digit number, ensuring that number is unique across the different types you're going to put into the list, and then using that id in the UI. It seems that the real issue you're running into is how to combine the lists where ids might overlap, and perhaps it may be feasible for you to devise a way to assign a non-overlapping ID.
Another possibility for assigning non-overlapping IDs, without having to come up with your own scheme or checking for uniqueness across multiple tables is to use a UUID for the lookup ID in the UI drop-down.

Related

Rails 4 many to many relationship

I have an app that has 2 models that need a many to many relationship. I originally tried using has_and_belongs_to_many but it wasn't working. After scrummaging through the internet I was convinced that this was not what I needed anyhow. So, I switched to a has_many through method. However, this isn't working either. I am now at a major loss on what the issue is. I have ripped it out many times and tried tutorial after tutorial but to no avail.
The models I am trying to connect are Message and Author. In my most recent attempt, I created a joining model (with corresponding table) called Authoring. Here is my code:
message.rb
has_many :authorings
has_many :authors, through: :authorings
author.rb
has_many :authorings
has_many :messages, through: :authorings
authoring.rb
belongs_to :message
belongs_to :author
messages_controller.rb
def messages_params
params.require(:message).permit(:title, :summary, :date, :duration, :youversion, :hours, :minutes, :seconds, :authors)
end
My Messages form:
<%= f.label :speakers %><br />
<%= collection_select(:authors, :authors, Author.all, :id, :name, {}, {:multiple => true, :size => 10}) %>
When I created a new message, this showed in the log:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"OcLeke/eRRzwApoMSgQF81kBCm7TaGxW1PoVAUgEcvo=", "message"=>{"title"=>"Test1", "summary"=>"This is my test summary.", "date(2i)"=>"8", "date(3i)"=>"25", "date(1i)"=>"2014", "date(4i)"=>"01", "date(5i)"=>"00", "hours"=>"0", "minutes"=>"58", "seconds"=>"52", "youversion"=>""}, "authors"=>{"authors"=>["", "1"]}, "commit"=>"I'm Finished", "id"=>"1"}
The form obviously recognizes the authors in the form but it doesn't save them in the database. What is my disconnect? Please help!!!
Thanks!
authors records are not getting created because the controller is expecting authors to be present within message key's value in params hash BUT if you notice the params hash in the log
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"OcLeke/eRRzwApoMSgQF81kBCm7TaGxW1PoVAUgEcvo=",
"message"=>{...}, "authors"=>{...}, "commit"=>"I'm Finished",
"id"=>"1"}
you would see that authors is coming as an altogether separate key in params hash which is why it is being ignored.
What you need to do is associate the authors with the form object, instead of using collection_select you need to use f.collection_select as shown below. Specify the first argument as :author_ids in f.collection_select.
<%= f.collection_select(:author_ids, Author.all, :id, :name, {}, {:multiple => true, :size => 10}) %>
As you have :multiple => true option specified, you would receive authors as an Array in the params hash. You need to permit author_ids as an Array in message_params method
def messages_params
params.require(:message).permit(:title, :summary, :date, :duration, :youversion, :hours, :minutes, :seconds, :author_ids => [])
end

Can't see why I'm getting undefined method form_for

I don't get why I'm getting this error
undefined method `sector_id' for #<Portfolio:0x007fe17c2e3848>
I have a Portfolio Model and a Sector model, they look like so
class Portfolio < ActiveRecord::Base
belongs_to :sector
attr_accessible :overview, :title, :sector_id
end
class Sector < ActiveRecord::Base
has_many :portfolios
attr_accessible :name
end
My routes
resources :portfolios do
resources :sectors
end
So within my form to create a new portfolio I have this collection_select
<%= f.label :sector_id, "Choose Sector", :class => 'title_label' %><br>
<%= f.collection_select(:sector_id, Sector.all, :id, :name, :prompt => "Please Select a Sector") %>
This is something I've done many times before and it has worked, can anyone see why I would be getting this error?
The only thing I can think of is that I have called my controller for portfolio as Portfolios, I always get mixed up with plural and singular controller names, would this make a difference in my case?
Maybe you have not run the migration yet that adds the column "sector_id" in your table "portfolios". If you are using MySQL connect to your database and check the table (show create table portfolios;). Use appropriate method to get this info from your database server if you are using other rdbms. Alternatively, in your rails console (rails c) type in Portofolio and see what attributes it prints out. Does it include sector_id?

Rails nested form on many-to-many: how to prevent duplicates?

I've setup a nested form in my rails 3.2.3 app, it's working fine, my models are:
class Recipe < ActiveRecord::Base
attr_accessible :title, :description, :excerpt, :date, :ingredient_lines_attributes
has_and_belongs_to_many :ingredient_lines
accepts_nested_attributes_for :ingredient_lines
end
and:
class IngredientLine < ActiveRecord::Base
attr_accessible :ingredient_id, :measurement_unit_id, :quantity
has_and_belongs_to_many :recipes
belongs_to :measurement_unit
belongs_to :ingredient
end
As above, a Recipe can have multiple IngredientLines and vice versa.
What I'm trying to avoid is record duplication on IngredienLine table.
For example imagine that for recipe_1 an IngredientLine with {"measurement_unit_id" => 1, "ingredient_id" => 1, "quantity" => 3.5} is associated, if for recipe_5 the IngredientLine child form is compiled by the user with the same values, I don't want a new record on IngredientLine table, but only a new association record in the join table ingredient_lines_recipes.
Note that currently I dont't have any IngredientLine controller as saving and updating IngredientLines is handled by nested form routines. Even my Recipe controller is plain and standard:
class RecipesController < ApplicationController
respond_to :html
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(params[:recipe])
flash[:notice] = 'Recipe saved.' if #recipe.save
respond_with(#recipe)
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.destroy
respond_with(:recipes)
end
def edit
respond_with(#recipe = Recipe.find(params[:id]))
end
def update
#recipe = Recipe.find(params[:id])
flash[:notice] = 'Recipe updated.' if #recipe.update_attributes(params[:recipe])
respond_with(#recipe)
end
end
My guess is that should be enough to override the standard create behavior for IngredientLine with find_or_create, but I don't know how to achieve it.
But there's another important point to take care, imagine the edit of a child form where some IngredientLines are present, if I add another IngredientLine, which is already stored in IngredientLine table, rails of course should not write anything on IngredientLine table, but should also distinguish between child records already associated to the parent, and the new child record for which needs to create the relation, writing a new record on the join table.
Thanks!
in Recipe model redefine method
def ingredient_lines_attributes=(attributes)
self.ingredient_lines << IngredientLine.where(attributes).first_or_initialize
end
Old question but I had the same problem. Forgot to add :id to white list with rails 4 strong_parameters.
For example:
widgets_controller.rb
def widget_params
params.require(:widget).permit(:name, :foos_attributes => [:id, :name, :_destroy],)
end
widget.rb
class Widget < ActiveRecord::Base
has_many :foos, dependent: :destroy
accepts_nested_attributes_for :foos, allow_destroy: true
end
foo.rb
class Foo < ActiveRecord::Base
belongs_to :widget
end
I have run into a similar situation and found inspiration in this answer. In short, I don't worry about the duplication of nested models until save time.
Translated to your example, I added autosave_associated_records_for_ingredient_lines to Recipe. It iterates through ingredient_lines and performs a find_or_create as your intuition said. If ingredient_lines are complex, Yuri's first_or_initialize approach may be cleaner.
I believe this has the behavior you're looking for: nested models are never duplicated, but editing one causes a new record rather than updating a shared one. There is the strong possibility of orphaned ingredient_lines but if that's a serious concern you could choose to update if that model has only one recipe with an id that matches the current one.

Rails populate collection_select

I am trying to auto populate a select box in rails with a relational table.
Venture_Users has a list of Users and Venture ID's so I want to find all Users under a specific Venture and then display the users via user_id, and user.name.
I tried the following
<%= f.collection_select(:user_id, VentureUser.find_all_by_venture_id(#venture.id), :user_id, :name) %>
However the last attribute :name doesn't work because it is not directly in my results and I need to run a query on my user table to get the name of the user.'
Essentially what I need to but don't know how to do is modify my VentureUsers.find_all statement to join attributes from my user table.
Thanks,
Mike
This should be quite straight forward as long as you have your associations set correctly. If I have understood your scenario correctly then you should probably have it set up like this:
class Venture < ActiveRecord::Base
has_many :venture_users
has_many :users, :through => :venture_users
end
class VentureUser < ActiveRecord::Base
belongs_to :venture
belongs_to :user
end
If your associations indeed look like this, then you should be able to create the select like this:
<%= f.collection_select(:user_id, #venture.users, :id, :name) %>

Rails 3 - Active_admin select nested object in many to many

I have two models. Deals and Stores.
I want to add already created Deals when creating a Store.
I want to add already created Sores when creating a Deal.
I am trying to use f.has_many, but I can't make it working.
My relationship is built using has_and_belongs_to_many :deals and has_and_belongs_to_many :stores (in the models)
My store custom form has the following:
f.inputs "Deals" do
f.has_many :deals do |deal|
deal.input :id, :as => :select, :include_blank => false
end
end
I don't know how to add deals to the store.
Any help?
Just checking, but do you have an
accepts_nested_attributes_for :deals #, :allow_destroy => true
declaration in your Store model, and an
accepts_nested_attributes_for :stores
declaration in your Deals model?
You may want to check these two pages:
http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for
https://github.com/justinfrench/formtastic (search in the page for accepts_nested_attributes_for)

Resources