Ruby on rails - nested forms with has_many through - ruby-on-rails

Basically, I want to write a web-based curation tool for clinical conditions (disease) and their underlying genetics. Say, I have a clinical condition (class: Phenotype), and I have "mutations"(class: Genotype) that belong to that condition - either individually (i.e. a given mutation is directly causing this condition) or as a group (two or more mutations together cause the condition). Each condition can have one or more of such groups (i.e. may be cause by different mutations or groups of mutations). So I figured I need to create a grouping class (class: GenotypeGroup) to make that association. What I cannot figure out is how to do the form... First, I would want to enter a phenotype with some description. I then would like to use the "show" view to add a new genotype_group to that phenotype (Add new genotype group). This would have to create, implicitly (since it is basically only a cross-reference table) the genotype_group and one or more genotypes which it links to the phenotype entry.
Right now, I have:
class Phenotype
has_many :genotype_groups
has_many :genotypes, through: :genotype_groups
accepts_nested_attributes_for :genotype_groups
end
class GenotypeGroup
belongs_to :phenotype
has_many :genotypes
accepts_nested_attributes_for :genotypes
end
class Genotype
belongs_to :genotype
end
And zero idea how this would work in terms of nested forms. If anyone has a helpful web resources (been googling for > 1hour now, but apparently don't even know what the thing I am trying to do is called..) - that would be great!
Cheers,
M

Turns out I was missing two things:
a) When nesting the genotype object, I need to add a "Genotype.new" to the nested form element:
<% f.fields_for :genotypes, Genotype.new do |gt| %>
something_here
<% end %>
b) I had to declare which variables should be carried (i.e. are permitted) by the params object in the respective controller(s) so that I could pass those values between the classes during the nested object creation.

Related

Confused about using or not using nested attributes

I would like to know the use cases for nested attributes. The pros and cons of using vs. not using it.
I have a model that has a lot of has_many associations. Example:
class Post
has_many :visitors
has_many :pageviews
has_one :metric
end
Although I like the idea of just sending one request and having all of those things created with the correct associations, I don't like the idea that all the creation of the visitors, pageviews and metric are in the PostsController. I very much like the separation of concerns. Is there any clear rule that I should follow when dealing with nested attributes?
Thank you.
In data-modelling we sometimes split up stuff over different tables/models, and imho nested models/nested forms are mostly used where the nested model has no reason to exist without the parent and vice versa. E.g. a person with their addresses: when creating a person we immediately need to add their address because (for instance in a delivery situation) a person without an address makes no sense.
Nested forms, where we can edit parent/child models as a whole, was popular and imho still has its benefit/place in some very specific situations (like the aforementioned example).
But in most cases, where the creation of the parent is not dependant on the child, I rather prefer to use ajax calls. I present all the information on a page, but when adding a child I make sure it is immediately saved and stored in the database (using their own controller --while visiting the parent's show page for instance).
I believe in most cases that the default/standard mega form is not the best UI/UX solution, and I believe that should be the main driver how to build your pages.
Coming back to your example: these should never be shown in one huge form, nobody is going to edit a post, and the stats, right? Statistics are collected and shown, but hopefully never "created". Normally one tracks actual pageviews, visitors ...
I think you're making a leap you don't need to.
I don't like the idea that all the creation of the visitors, pageviews and metric are in the PostsController.
They shouldn't be and don't have to be. In fact, they never are by default.
Run rails routes in your console and you'll see each of those has_many models have their own controllers and views.
It is very rare I have a web app with a model that doesn't have and use its own controller.
A route like /posts/:id/visitors should point to visitors#index, not something like posts#visitors
If you're putting everything into one controller, I'd argue you aren't actually nesting, you're expanding.
And I'd argue the point of relational databases is to have relationships, so limiting your relationships seems kinda self-limiting.
I don't think you have really grasped what nested attributes is used for in the first place. Its used when you need to CRUD a resource and its children in a single request.
Its use is really dictated by the user experience requirements. Sometimes you actually need a single form like this very common order form example:
class Order
has_many :line_items
accepts_nested_attributes_for :line_items
end
class LineItem
belongs_to :product
end
<%= form_for(#order) do |f| %>
<%= f.fields_for(:line_items) do |ff| %>
<%= ff.number_field :quantity %>
<%= ff.collection_select :product_id, Product.all, :id, :name %>
<% end %>
<% end %>
class OrdersController < ApplicationController
# ...
def update
#order = Order.find(params[:id])
if #order.update(order_params)
# ..
else
# ...
end
end
private
def order_params
params.require(:order)
.permit(:line_items_attributes: [:quantity, :product_id, :_destroy])
end
end
This is just that common checkout form where users can change the number of products in their cart. It lets the users manipulate multiple records at once in a plain old synchronous form.
That said nested attributes is probably one of the most misused components of Rails though and everything beyond 1 level of nesting usually ends up in a hideous mess. Its also a very common misconception that it should be used to assign associations which should in most cases just by done by adding selects or checkboxes that point to the _id or _ids attributes created by the associations.
If you are using it just to mosh everything into a single controller because "I don't want to have too many classes. Waaah" then yes its a huge anti-pattern.
The alternative really is using AJAX to let the user CRUD child records without reloading the page.
I would say that your example is not a good candidate for nested attributes. Are any of those associations actually even created by the user?

Limiting an attribute in Rails

I have a Tournament model that needs 0, 1, or 2 contacts. I created a Contact model and set has_many :contacts on the Tournament and belongs_to :tournament on the Contact. The Tournament accepts_nested_attributes_for :contacts.
However, when I build the form for Tournament I don't quite see how I should do it. I'm thinking about having two fields_for :contacts but it feels messy. I also considered having two specific attributes on the Tournament model (something along the line of primary_contact and secondary_contact) but I'm not sure about how to do that.
Is there a "correct" way to do this? Any suggestions?
I'm on Rails 3.1 BTW.
fields_for :contacts is the right way to go.
Take advantage of the fact that, if tournament.contacts has multiple items, then a single fields_for :contacts will show multiple fieldsets.
Then take advantage of the fact that tournament.contacts.build will create an unsaved Contact and add it to the contacts collection. If you do this in the controller before showing the form then your fields_for will display this empty contact and use it correctly with its nested attributes
I think you shouldn't limit the contacts for 2 fields, because I think you should keep the flexibility of adding more contacts for a tournament later
I have done a small example (by using check boxes) between Project to users, you might be able to get idea
https://github.com/sameera207/HABTMsample
I'd suggest maybe adding a non-persistent contact_list attribute and then you could enter as many contacts as you need separated by commas into one field:
has_many :contacts
attr_accessor :contact_list
def contact_list=value
value.split(',').each do |email|
self.contacts.build(:email => email).save
end
end
def contact_list
self.contacts.join(',')
end
If you need to enter more information for each contact (not just a name, email, or phone number), then you would need more fields.
The following railscast may help you:
http://railscasts.com/episodes/196-nested-model-form-part-1

Challenging Rails Question with Form Based on Existing Resources

You have a set of related models created through a scaffold e.g. a house, which has many rooms, which each have many windows, which each has a selection of locks.
These resources are already full of data i.e. someone has entered all the information, such as: a room called 'kitchen' has various windows associated with it and these windows each have five different locks associated with them.
Someone comes along and says:
Can you create a form that lets someone create a new project where they can select the different rooms, windows and then specify the locks that they would like for that project? (these are already in the system, nothing new to add, just the associations to a new project)
This sounds like a nested form but I have wasted a lot of time trying to solve this - there are many levels of nesting, which make this tricky. Any suggestions?
session based solution
With such deeply nested models select box on the front end wouldn't be enough...
Assuming this, you may want to create a current_house who's id live in a session (just like current_user works).
Once you have your current_house add different items by navigating to your list of items view and clicking on the add_to link :
# house_controller.rb
def add_to
current_house.polymorphic_items << Kitchen.find(params[:id])
redirect_to :back
end
But there are many approaches to this session based solution which sort of implements a cart/order system. You may want to add a current_item to add stuff in each leaf of your tree aka room of your house.
E.G after clicking on the kitchen you just added :
before_filter :set_current_item
def add_to
current_item.windows << Window.find(id)
end
current_item beeing polymorphic : a living room, a bathroom etc.
But how you implement that precisely depends on your Domain Model....
As a rule of thumb regarding nested forms I'd follow rails guidance for routes : don't go deeper than one level or you'll end up in a mess.
Yes this is a nested form. Railscasts nested forms is a great place to start.
If everything is already in the system you probably just want select boxes so they can select what they want. Also check out the .build method. If you have multiple levels of nesting you can also manually set the association by passing in the foreign key yourself.
I think you can model this with a single level of nested attributes, given the models below (based on Windows/Locks pre-existing and a room just needing to mix and match them into a set of windows with given locks):
class House < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :house
has_many :window_configs
end
class WindowConfig < ActiveRecord::Base
belongs_to :room
belongs_to :window
belongs_to :lock
end
class Lock < ActiveRecord::Base
has_many :window_configs
end
class Window < ActiveRecord::Base
has_many :window_configs
end
... based on that model setup, you could have a single house form that you dynamically add child 'room' definitions to that each have a name and a collection of window_configs which have two select boxes for each one (choose a window definition and then a lock definition). Because you're dynamically adding multiple rooms with multiple windows, you'd need some JS to populate new form elements, but it could all live in a single nested form.
form_for :house do |form|
# Dynamically add a Room form for each room you want with js
form.fields_for :room do |room_attributes|
room_attributes.text_field :name
# Dynamically add window_config forms on Room w/ JS
room_attributes.fields_for :window_config do |window_attributes|
window_attributes.select :window_id, Window.all
window_attributes.select :lock_id, Lock.all

Rails 3 Polymorphic Associations Question

I've been trying to switch my Orders model to a polymorphic association with my Product and Service models. However, I have a few questions that I haven't been able to find answers to, even after watching the RailsCast and reading the documentation (so, those suggestions are appreciated, but I need a more concrete answer).
Question:
Is a polymorphic association the best thing to use in this case? Prior to this, I was using a Transaction model that had multiple belongs_to associations and used a custom Parent function to determine which one it was. This was working fine, but someone suggested a polymorphic association may clean things up.
I set up the polymorphic association properly and have been unable to have the transactable_id and transactable_type automatically populated. The code is below. I have side-stepped this by manually putting them in inside the form, but if anyone knows the proper way to do it, that would be great!
How can I access elements with polymorphic associations? For example, in my Cart object (which has_many Transactions and which Transactions belongs_to) I can no longer access things using #cart.transactions.each do |t| ... #t.product.name type coding.
My model associations look like this:
class Order < ActiveRecord::Base
belongs_to :orderable, :polymorphic => true
end
class Product < ActiveRecord::Base
has_many :orders, :as => :orderable
end
My forms used to look like this:
<% form_for [#orderable, #order] do |f| %>
...
<% end %>
And were rendered like this in my Product Show view:
<%= render 'orders/form' %>
Now, I pass a variable for the product.id in the render partial and use it to populate the transactable_id field. But, I feel like that is very messy.
Again, I have read the tutorials and API docs and have been unable to solve this, so any help would be greatly appreciated!!
Answers to your questions:
If your business login implies that multiple models will have related model with the same fields so you should use polymorphic association. (In your case you can use it).
If set up polymorphic association Rails will automatically handle setting *_id and *_type fields depending on associated parent model.
Lets say you have Product with many orders in polymorphic association and you want to define which model order belongs to:
order = Order.first
order.orderable

How to access and submit related polymorphic models in the same form, in Rails?

Suppose I have 3 models, Car, Motorcycle and Truck, and for each I have to enter a bunch of stuff, such as a list of known previous owners, traffic tickets, license plates, etc. So I created a model for each (PreviousOwners, PreviousPlates, etc) and set up polymorphic associations for the related models.
The problem is, how can I enter all of that using just one form, kind of like this:
Car #123
Known previous owners:
Jason Jazz
Brian Bass [add another]
Known previous license plates:
12345
67890 [add another]
Current status:
Cleared
(this is a dropdown select menu, CurrentStatus is also a polymorphic association, but with predefined values.)
etc
This is proving to be a bitch, way beyond my level of expertise (newbie here). The resources are not nested and almost everything I find on multiple models is for nested resources, and nothing seems to apply to polymorphic associations.
(This is just an example, I know ideally I should have a Vehicle model with 'Car', etc, as categories, but it's just to illustrate the real need for polymorphic models in my case.)
Thanks.
Maybe the PresenterPattern is helpfull too:
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
The basic idea is to create a presenter which acts like a model and processes all the incoming data from your form and distributes it to the models. This way it's also easy to create multiple instances of lets say PreviousOwner and attach it to Car.
Check the link out!
You can use the new nested attributes in Rails 2.3, but there is a certain way you have to write it to make it work. The trick is that you need to create the actual polymorphic object, then build the class that has the belongs to polymorphic clause in it. This is an example I found at Ryans Scraps, posted by a user named: Superslau (I've cleaned it up a good bit for here):
This feature is really awesome. I have
implemented this with polymorphic
associations, and it works!
class Task < ActiveRecord::Base
has_many :assets, :dependent=>:destroy
accepts_nested_attributes_for :assets, :allow_destroy => true
belongs_to :workable, :polymorphic => true
end
class Upload < ActiveRecord::Base
has_one :task, :as => :workable, :dependent=>:destroy
accepts_nested_attributes_for :task, :allow_destroy => true
end
Upload is a kind of task. All tasks
can have one or more assets uploaded.
I took me a while to figure out that I
should use the Upload model as the
parent. So in one form, I can create
an upload, and it’s corresponding task
entry, along with a file upload.
in my controller:
def new
#upload = Upload.new
#upload.task = Task.new
#upload.task.assets.build
end
Don’t
worry if that doesn’t make any sense,
I just wanted to let people know that
accepts_nested_attributes_for works
just fine with polymorphic
associations. Thanks Eloy!
Very well, nested form builders doesn't have to be associated with nested resources AFAIK.Can you post your models code as well?
There is a RailsCast on Complex Forms that might help you with building a single form from multiple models.
If the car/motorcycle/truck models are identical, you should add a type column to your vehicle model. If they're not, you should use STI (single table inheritance).
But yeah, need to see your models first before I can give you code.
You can avoid this and make things a bit simpler by introducing a Vehicle model. The Vehicle model can have all your PreviousOwners, PreviousPlates, etc collections, and then your Truck, Car and Motorcycle models can has_one Vehicle.

Resources