creating complex nested forms with cocoon - ruby-on-rails

I've created a complex nested form, but I'm having trouble with the part where my app creates/edits records based on the input from that form correctly.
To-Do scenario: I need to enter Tasks one-by-one into my form (one task per "submit" press), from a pile of little pieces of papers with tasks written on them. Each Task has a :project_number, a :project_number_type, and an :owner written on it. In my app, these attributes are in the models ProjectNumber (for the first two) and Owner, for the last one. (I had to make a separate ProjectNumber model because my Projects can at times have multiple :project_numbers of different project_number_types.)
class Owner < ActiveRecord::Base
has_many :projects
has_many :tasks, :through => :projects
end
class Project < ActiveRecord::Base
belongs_to :owner
has_many :tasks
has_many :project_numbers
end
class ProjectNumber < ActiveRecord::Base
belongs_to :project
end
class Task < ActiveRecord::Base
belongs_to :project
delegate :owner, :to => :project, :allow_nil => true
end
My form shows the correct nested fields_for fields for each of the attributes mentioned above. (I omitted things like my accepts_nested_attributes_for).
2 related (I think) issues:
My form never creates any new projects! The Cocoon gem I'm using to create nested forms has me creating form partials for each nested model. In the Project partial, since my Project model doesn't have a :number attribute itself, but instead has an association to the ProjectNumbers model which does, I just have a reference to the ProjectNumbers partial and its attributes.
I need my app to edit an existing Project with the new Task if the Task's :project_number, :project_number_type, and :owner all match those associated with an existing Project. Otherwise, it should create a new Project for that Task to belong to.
I read lots of little bits of code here and elsewhere, but I need something just a little more, maybe that includes the "what" as in .first_or_initialize and also the "where", like as in /models/task.rb, or tasks_controller.rb? Maybe I need to create something custom? Thanks so much!
Edit: Here are the form partials
==== /views/tasks/_form.html.slim
= simple_form_for #task do |f|
#project
= f.simple_fields_for :project do |project|
= render 'project_fields', :f => project
==== /views/tasks/_project_fields.html.slim
.nested-fields
.project_numbers
= f.simple_fields_for :project_numbers do |project_number|
= render 'project_number_fields', :f => project_number

Related

Rails fields_for non-nested models

I have a rails app with the following:
class Habit < ActiveRecord::Base
has_many :habit_journals
has_many :road_blocks
class RoadBlock < ActiveRecord::Base
belongs_to :habit
has_many :road_block_histories
class HabitJournal < ActiveRecord::Base
belongs_to :habit
end
I have a form that creates HabitJournals, although within this form I am trying to create RoadBlockHistories (which is just a rating on a RoadBlock over time)
I can't seem to work out how to create the form for this.
Habits have many HabitJournals and RoadBlocks. RoadBlockHistories don't relate directly to HabitJournals, but I would still like to capture these at the same form submission.
Any help would be appreciated.
Cheers
M
Sorry, I realised I need to add additional info.
This is Rails 4.1
A user will create a habit such as 'Be healthier'.
They then will create a number of road blocks to this habit such as 'Eating chocolate at night' and 'sleeping in instead of exercising'.
When the user views a habit they can add a journal entry (an update of how they are doing establishing the habit).
When adding a journal entry I'd like the user to be displayed with all their "road blocks" with a dropdown to select a rating (which is a mark out of 10 of how they are going with that road block). This rating would create a road_block_history object.
Hopefully this clarifies.
Cheers
M
Objects
I understand what you want - still trying to consider how to get it working.
Something you need to consider is the nature of Ruby on Rails (Ruby in particular). Ruby is an object orientated language, meaning everything it does is based around objects.
The problem you have is the object you're trying to create ("RoadBlockHistories") has no direct association to your "parent" object that you're trying to create ("HabitJournals").
accepts_nested_attributes_for
The "way" you'd surmount this problem typically is to use the accepts_nested_attributes_for method - basically allowing you to pass data from a parent model to a child.
The important thing to note here is that this only works if your models are directly associated, IE that it's either a has_many, belongs_to or has_many :through for the model you're trying to create.
This is not magic - it's core Ruby. If you have a model you're trying to populate, if it has associated data, you'll be able to send that through your original model. However, as the language is object orientated, you cannot just populate an unrelated object without having that association
Fix
From what you've written, I would certainly look at treating the two forms as separate data objects (IE not associating them)
Perhaps you could do this (although it will need tweaking):
#app/models/habit.rb
class Habit < ActiveRecord::Base
has_many :habit_journals
has_many :road_blocks
end
#app/models/road_block.rb
class RoadBlock < ActiveRecord::Base
belongs_to :habit
has_many :road_block_histories
end
#app/models/habit_journal.rb
class HabitJournal < ActiveRecord::Base
belongs_to :habit
has_many :road_blocks
end
Although this is not tested, nor do I think it would work, the reason I included this is to give you the idea about how you could pass an "unassociated" object through to Rails.
You can actually pass objects / data through others - so you could actually use something like the following:
#app/controllers/habit_journals_controller.rb
Class HabitJournalsController < ApplicationController
def new
#habit_journal = HabitJournal.new
#habit_journal.road_blocks.build.road_block_histories.build #-> again, not sure if will work
end
def create
#habit_journal = HabitJournal.new(habit_journal_params)
#habit_journal.save
end
private
def habit_journal_params
params.require(:habit_journal).permit(:x, :y, :z, road_blocks_attributes: [road_block_histories_attributes:[] ])
end
end
If the associations are correct, this should allow you to create the following form:
#app/views/habit_jorunals/new.html.erb
<%= form_for #habit_journal do |f| %>
<%= f.fields_for :road_blocks do |rb| %>
<%= rb.fields_for :road_blocks_histories do |h| %>
<%= h.text_field :rating %>
<% end %>
<% end %>
<% end %>

Rails 3 Polymorphic nested attributes

I am trying to build a polymorphic relationship from a nested form that's backwards to all the examples I've found. I am hoping someone to point out the error of my ways.
class Container < ActiveRecord::Base
belongs_to :content, :polymorphic => true
end
class Notice < ActiveRecord::Base
has_one :container, :as => :content
end
class Form < ActiveRecord::Base
has_one :container, :as => :content
end
It seems most people would build a Container from a Notice or Form, but in my case the notice or form contains a small amount of content (file location or a couple db fields) so it's much dry'er to build the Notice or Form from the Container.
I thought I could solve by adding accepts_nested_attributes_for :content but that gives me an unrecognized attribute :notice when I try to create a Container with a nested Notice (looking for content, not the polymorphic association)
I can do it manually and explicitly exclude the nested fields like
if params[:container].has_key('notice')
#c = Container.new(params[:container].except(:notice))
and then build, but isn't that a smell? Is there a better way?
Thank you for reading!
Nested attributes are designed to work from the parent down to the children, not the other way around. Moreover, in this scenario, how would nested attributes know whether you are trying to create a Notice or Form object?
If you find it DRYer to build the content from the container, you probably have your associations inside out - try changing your schema to:
class Container < ActiveRecord::Base
has_one :notice
has_one :form
end
class Notice < ActiveRecord::Base
belongs_to :container
end
class Form < ActiveRecord::Base
belongs_to :container
end
You can use validation to ensure only one child (:notice or :form) is actually associated if need be.

Nested form - how to add existing nested object to parent form?

I've got my models setup for a many-to-many relationship:
class Workshop < ActiveRecord::Base
has_many :workshop_students
has_many :students, :through => :student_workshops
accepts_nested_attributes_for :students
end
class Student < ActiveRecord::Base
has_many :student_workshops
has_many :workshops, :through => :student_workshops
accepts_nested_attributes_for :products
end
class StudentWorkshop < ActiveRecord::Base
belongs_to :student
belongs_to :workshop
end
As you can see above, a student can have many workshops and workshop can have many students.
I've looked at the following Rails casts: here and here. And most of the online sources I stumble across only show how to do nested forms for creating new objects within the parent form.
I don't want to create a new object. I only want to add an existing object to the parent form. So for example. If I decide to create a new workshop, I'd like to assign existing students to the workshop.
One thing I don't understand is, how do I link students into the workshop form? Second, when the params are passed, what should be in the controller method for update/create?
If anyone can point me to the right direction, I would appreciate it.
The easiest thing to do is:
<%= f.collection_select(:student_ids, Student.all, :id, :name, {:include_blank => true}, {:selected => #workshop.student_ids, :multiple => true} )%>
You should not have to do anything in the create action.
Ok, for anyone coming across the same issue in the future. The solution I came up with was in def create. I am able to access a POST attribute called student_ids, which comes in the form of an array

Rails 3: Having trouble with nested forms and has_many :through

Here's my model set up.
Band Model
has_many :bands_genres
has_many :genres, :through => :bands_genres
Genre Model
has_many :bands_genres
has_many :bands, :through => :bands_genres
BandsGenre Model
belongs_to :band
belongs_to :genre
I have a form where you can add a new band and then select a genre from a dropdown field that pulls from the pre-set genres in the genre model.
So what I ultimately need to do is set up a form so that when a band adds their band and select a genre, it creates the correct join in the bands_genre model.
Not sure where to start with setting up the form, controllers and models for this.
I'm running Rails 3.0.3
There are quite a few text/video casts covering this, since its a popular use case. I would encourage you to look at:
http://railscasts.com/episodes/73-complex-forms-part-1 or its equivalent asciicast (which is a text based cast of the video).
Further I would recommend you use formtastic. Associations are managed automatically so it makes form building trivial and keeps your code tidy. And yes there are casts for that too.
http://railscasts.com/episodes/184-formtastic-part-1
Edit:
Band Model
has_many :genres, :as => :band_genres
Genre Model
has_many :bands, :as => :band_genres
Your genre table has a band_id, and your band table has a genre_id.
bands_controller
def new
#genres = Genre.all
#post = Post.new
end
posts/new.html.haml
(This part I'm a little unsure of, but it roughly goes like this)
- form_for #post do |f|
= f.select :genre_id, #genres, {}
= f.submit

Nested Forms and Building associations

I'm working on a add to cart form. It looks something like this
#Models
Order.rb
has_many :line_items
accepts_nested_attributes_for :line_items, :allow_destroy => true
LineItem.rb
has_one :product
belongs_to :order
Product.rb
belongs_to :line_item
I'd like to create a form in product#show to allow multiple related products to be added to the order/cart at once, basically create or update a line item for each product.
Probably something like this in the view (HAML to keep it brief).
-form_for #order do |f|
- if has_related?
- for related in #products.related_products
- f.field_for :line_item do |li_form|
= li_form.text_field :quantity
= li_form.hidden_field :product_id
= related.product_name
What would it take to actually make something like this work?
I would need more info to be sure, but it seems that a LineItem belongs_to :product and Product should NOT belong_to :line_item unless there really is a 1-1 relationship there (which wouldn't make sense to me, and doesn't follow the normal convention of these sorts of systems)
Note** using - before form_for and fields_for has been deprecated in rails 3 in favor of = since the form does actually render html
= fields_for :line_items do |li_form| is the syntax for a has_many relationship
The rest all depends on your user experience design.
Hope this helps!

Resources