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 %>
Related
I have a page that displays a lot of data and I'm working on optimizing it and I'm struggling with balancing reducing DB queries with getting the proper data in the order I need.
A Transaction is the top-level element, and each transaction has multiple workflows and each workflow has many milestones. I order the workflows and milestones by a position:integer attribute.
# Top-level element
class Transaction < ApplicationRecord
has_many :workflows
has_many :team_members
end
# Parent element for milestones
class Workflow < ApplicationRecord
belongs_to :transaction
has_many :milestones
end
# Unique individual
class Person < ApplicationRecord
has_many :team_members
end
# Acts as a join between Transactions and Persons
class TeamMember < ApplicationRecord
belongs_to :transaction
belongs_to :person
belongs_to :team_member_type
end
class TeamMemberType < ApplicationRecord
has_many :team_members
end
In my controller this is what I have:
def show
#transaction = Transaction.includes(
team_members: [:person, :team_member_type],
workflows: [:milestones]
).find(params[id])
end
This gathers all the data I need in 1 DB query, but the problem is when I iterate over the milestones for a workflow, I need it to be sorted by the position attribute.
I can grab the data from the DB and then sort it with Ruby, but that seems inefficient and I would prefer to get it all done in the DB properly (if possible).
If I were to write it with an N+1 issue, it would look like this:
<% #workflows.order(:position).each do |workflow| %>
<%= workflow.name %>
<% workflow.milestones.order(:position).each do |milestone| %>
<%= milestone.name %>
<% end %>
<% end %>
I think everyone agrees that's a bad idea, but I'm struggling to figure out how to sort the data and optimize my DB calls at the same time.
I also sort the team_members by the team_member_type.value attribute, but that's the same question as above.
<% #team_members.includes(:team_member_type, :person).order("team_member_types.value").each do |team_member| %>
...
<% end %>
My main objective is efficiency. If I need to use some Query objects to clean it up and raw SQL, I'm ok with that, I'm just not extremely proficient at SQL and would prefer using ActiveRecord if reasonable.
You can order by those two attributes in the controller method:
def show
#transaction = Transaction.includes(
team_members: [:person, :team_member_type],
workflows: [:milestones]
).order('milestones.position, team_member_types.value').find(params[id])
end
Let me know if this helps.
You could create associations that include the order_by, but my experience has been that that can silently fail when eager loading, leaving you with an unordered result.
Although you believe, probably correctly, that ordering in Ruby would be less efficient, I would at least benchmark it. The major performance problem is in the n + 1 query situation, and I would be optimistic that you'll find sorting in Ruby to be performant enough.
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.
I am using Rails 4.2.4. I know I have set my relations correctly but I'm getting undefined method "city" for #
support.rb:
belongs_to :user
user.rb:
has_many :supports (should the be plural?)
views/users/show.html.erb:
<%= #user.supports.city %>
In my supports table:
t.string :city
t.integer :user_id
I have a form for support in which I have filled out the city field and I can see in entry with Support.all in the rails console so Im sure the value for :city is saved in the db.
I have used rails g scaffold support for this process where a user can create many supports. Am I missed something?
has_many :supports should be plural
#user.supports returns all supports but it can return an empty array. So you have to use:
if support = #user.supports.first
# use support.city
end
or
<% #user.supports.each do |support| %>
<h1><%= support.city %></h1>
<% end %>
If you're trying to access associative data, you'll need to understand that pluralized relations (IE has_many) will return collections of data:
#app/models/support.rb
class Support < ActiveRecord::Base
belongs_to :user #-> #support.user
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :supports #-> #user.supports
end
To answer your question about the :plural, no, you don't need to call it a plural. However, as per Rails convention, it builds the entire relationship (and queries) off the back of the name:
belongs_to associations must use the singular term. If you used the
pluralized form in the above example for the customer association in
the Order model, you would be told that there was an "uninitialized
constant Order::Customers". This is because Rails automatically infers
the class name from the association name. If the association name is
wrongly pluralized, then the inferred class will be wrongly pluralized
too.
If you wanted to use singular names for your associations with has_many, you'll have to define your class etc:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :support, class_name: "Support", foreign_key: "support_id"
end
--
When you get your returned data from a has_many collection, you need to cycle through the data. Since it's a collection (as opposed to a "member" -- single record), you will need to something like the following:
<% #user.supports.each do |support| %>
<%= support.city %>
<% end %>
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.
The title is obviously confusing so let me get down to it.
class Resume < ActiveRecord::Base
has_many :references
end
class Reference < ActiveRecord::Base
has_one :phone
end
class Phone < ActiveRecord::Base
end
So I'm using cocoon and to have the simple_fields_for :phone populated I need to run build_phone on every reference object created.
Similarly I had resume has_one basic_info has_one phone.
And I was able to build it like so
#resume.build_basic_info
#resume.basic_info.build_phone
But in this case I have:
#resume.references.build
#resume.references.first.build_phone
Give me the error unknown attribute: reference_id.
So how do I build the phone association for references in the cocoon form and have it built on every instantiation of a new reference in cocoon?
Edit: I didn't have reference_id in phone. And thanks to #vee I have a much better way of handling existing models. So the code works now.
Still need to figure out how to run build_phone on every new call to link_to_add_association in cocoon.
You can pass a block when you build references as:
#resume.references.build do |r|
r.build_phone
end