Rails: Polymorphic Associations - Create Child In Form And Choose Parent - ruby-on-rails

To illustrate my question, i'll use the following association as listed on ruby guides for polymorphic associations:
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
Now in our :pictures table, we will have 2 different columns, namely imageable_id and imageable_type.
The first thing that I am unclear of is, what exactly goes into imageable_type ? Is that something that the "rails magic" would automatically fill up when imageable_id is declared?
Moving on to my next point (which will probably indirectly answer my uncertainty above), how do I assign either a Product or Employee to my Picture.new form?
#create new picture form
#assume pre_created_array holds the :id for both Employee & Product
<%= form_for #picture do |f| %>
#relevant fields ontop
#choosing parent field is the one im unsure of
#this is what i assumed might be correct(?)
<%= f.select :imageable_id, options_for_select(pre_created_array) %>
<% end %>
Now does this form actually work? Is the building of the association something that has to be handled in the controller action instead? I am actually not very sure because usually in a regular association the parent can be declared before .save, such as doing #post.employee = #find an employee. So are we suppose to read the :imageable_id?
#pictures controller
def create
#picture = Picture.new(picture_params)
#manipulate :imageable_id here???
if #picture.save
#blahblah other code
end
So i am actually quite unsure about this, whether it is supposed to be the form or controller handling the building of association. Which is why i'm bringing up this 2 "uncertainties".

A polymorphic association is cool because it allows a single table to belong_to multiple, as you know. So given a picture, we don't have to know whether it belongs to an employee or a product, we can just call picture.imageable and get the parent.
So if we don't have to know where the parent is, that must means Rails has to know. How does it know? imageable_type! imageable_type is the name of the class which it belongs to. In this case, either 'Employee' or 'Product'. That way, given the imageable_id, it knows what table in which to search.
image.imageable actually calls image.imageable_type.constantize.find(image.imageable_id)
Rails will do this "magic" for you if you simply assign objects instead of IDs. image.update(imageable: Product.first) will assign both for you.
So in your form, you should be able work with a collection of objects and let Rails do the rest for you.

Related

Many to Many relation issue in Rails 4

I am a rails beginner and encountered the following issue
Models are setup as follows (many to many relation):
class Activity < ActiveRecord::Base
belongs_to :user
has_many :joinings
has_many :attendees, through: :joinings
end
class Joining < ActiveRecord::Base
belongs_to :activity
belongs_to :attendee
end
class Attendee < ActiveRecord::Base
has_many :joinings
has_many :activities, through: :joinings
end
This is one page test application for some users posting some activities, and other users to join the activities.
It is organized as single page format (activities index), and after each activity, there is a "Join" button users can click.
I am stuck at the point when a user needs to join a specific activity.
in the index.html.erb (of the activities), with the Join button code.
This will point to the attendee controller, to Create method, but I got no information regarding the Activity that I want to follow (eg. activity_id, or id)
Without this I cannot use the many to many relation to create the attendee.
What would be the correct button code, or any other suggestion to to get the corresponding activity ID in the attendees controller?
I tried a lot of alternatives, including even session[current_activity] , but is pointing (of course) always to the last activity.
Thanks so much !
If you have existing activities, and existing attendees, and you want to change the relationship between them, then you are editing the join table records. Therefore, you should have a controller for these. Like i said in my comment i'd strongly recomnmend renaming "joining" to "AttendeeActivity" so your schema looks like this:
class Activity < ActiveRecord::Base
belongs_to :user
has_many :attendee_activities
has_many :attendees, through: :attendee_activities
end
class AttendeeActivity < ActiveRecord::Base
belongs_to :activity
belongs_to :attendee
end
class Attendee < ActiveRecord::Base
has_many :attendee_activities
has_many :activities, through: :attendee_activities
end
Now, make an AttendeeActivitiesController, with the standard scaffoldy create/update/destroy methods.
Now, when you want to add an attendee to an activity, you're just creating an AttendeeActivity record. if you want to remove an attendee from an activity, you're destroying an AttendeeActivity record. Super simple.
EDIT
If you want to create an Attendee, and add them to an activity at the same time, then add a hidden field to the form triggered by the button:
<%= hidden_field_tag "attendee[activity_ids][]", activity.id %>
This will effectively call, when creating the attendee,
#attendee.activity_ids = [123]
thus adding them to activity 123 for example.
You have several options here. I'm assuming that the Join button will simply submit a hidden form to the attendees controller's create action. So the simplest solution would be to include the activity_id as a hidden form tag that gets submitted along with the rest of the form. This activity_id will be available in the params hash in the controller.
The other option is to setup Nested routing so that the activity_id is exposed via the path.
Thanks for all the details. I will change the naming of the join table for the future.
My problem was that I could not find the specific activity for attendee create method. Finally I found something like this for the JOIN button:
<%= button_to 'Join', attendees_path(:id => activity) %>
This will store the Activity ID, so I am able to find it in the Attendees controller:
def create
activity = Activity.find(params[:id])
#attendee = activity.attendees.create user_id: current_user.id
redirect_to activities_path
end
this updates also the Joinings (AttendeeActivity) table.
I will study about the hidden_field_tag solution, as is not clear to me yet.

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 %>

Add variable number of items of one type when creating item of another type

I want to create a form that lets you create an item of type A. In that form, you can add multiple items of type B to be associated to the item of type A that you are creating.
I think I understand how to do this in the models- just have a has_many and belongs to relationship.
I'm not really sure how to do this in the UI. Right, now I have the scaffolding and it only has fields for attributes of the item I'm creating. Is there a way to have it show fields for adding items of types defined in has_many that we see in the model file?
EDIT
ClassA
has_many :ClassB
ClassB
belongs_to :ClassA
Let's assume your Item A is a Person and Item B is a Project. So, for what you said, a Person has_many Projects. (I decided to use "real world" resources, instead of Item A and Item B for the clarify of the example).
Here is how you would define your models associations:
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects
end
class Project < ActiveRecord::Base
belongs_to :person
end
Then, the form of your Person would look like this:
<%= form_for #person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_builder| %>
# Here goes the fields of the projects
<%= project_builder.text_field :name %>
<% end %>
...
<% end %>
The key here is fields_for method, which will let you put into your Person form, form builders for the Projects associated with that Person.
It is important to know that if you are creating a new Person, there are obviously no Projects associated, and therefore the fields_for part would be empty. You might want to start by just basically doing this in the #new action:
def new
#person = Person.new
5.times { #person.projects.build }
end
Later, and once you get the feeling you know what's going on, you might want to add some buttons in the front end side that would let you "dynamically" add new Projects.

Selecting belongs_to parents in a form?

Imagine I have something like this:
class Employer < ActiveRecord::Base
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :employer
end
And I want to create a new Employee, and give a form with with a drop down box, where I can select which Employer I wish to associate it with. The dropdown should list every employer in the system. How can I do this?
Thanks.
I think you haven't understood what this association does. Or I haven't understood what you really want.
If you want to display ALL employers in a view, you simply have to fetch them in the corresponding controller action and save it in an instance variable. Then you can access its contents inside the view.
controller action:
#employers = Employer.all
...
corresponding view:
<%= collection_select #employers %>

Building and creating related objects via the association: how to set the foreign key of a nested model?

I am using Ruby on Rails 3.1.0. I am trying to save a nested model having an attribute that is intended to store the foreign key of the parent model. At the creation time of the parent model I would like to set that attribute value to the parent model id value.
In my model I have:
class Article < ActiveRecord::Base
has_many :article_category_relationships
has_many :categories,
:through => :article_category_relationships
# Accept nested model attributes
accepts_nested_attributes_for :articles_category_relationships
end
class Category < ActiveRecord::Base
has_many :article_category_relationships
has_many :articles,
:through => :article_category_relationships
end
# The join model:
class ArticleCategoryRelationship < ActiveRecord::Base
# Table columns are:
# - article_id
# - category_id
# - user_id
# - ...
belongs_to :category
belongs_to :article
end
In my view I have the following:
...
<% #current_user.article_categories.each do |article_category| %>
<%= check_box_tag 'article[articles_category_relationships_attributes][][category_id]', article_category.id, false %>
<% end %>
In my controller I have:
def create
#article = Article.new(params[:article])
...
end
In my case, the article_id (related to the ArticleCategoryRelationship nested model) should be set to the #article.id value after the Article creation and the problem is that the Ruby on Rails framework seems do not set that value at the creation time. In few words, considering my case, I would like to attach the foreign key automatically.
Just to know, the generated params when the form is submitted is:
"article"=>{"title"=>"Sample title", "articles_category_relationships_attributes"=>[{"category_id"=>"8"}, {"category_id"=>"9"}, {"category_id"=>"10"}] }
Is it possible to "auto"-set the foreign key (article_id) of the nested model? If so, how can I do that?
try using :
#a_particular_article.article_category_relationships.build(params[:something])
you can see here for more info, and might want to have a look at nested attributes and at validates_associated
In my opinion, you just CAN'T do that.
If you want to save articles_category_relationships, you need a article_id for each of them.
And, when you save article, rails will first validate article and all sub-objects. This means
article.valid? must be true before you can save any record.
Since article does not have an id before save to db, article_id in any of articles_category_relationships is empty.
Therefore, article.valid? will always be false as long as you need to CREATE new article and its sub-objects at the same time
To summarize, here is the steps of rails:
validate article itself, success!(NOTE: note saved)
validate articles_category_relationship for each of article.articles_category_relationships, article_id not provided, fail!
What you can do
create article first
assign params to this created article
update with params

Resources