I currently have a model for an assignment table in Rails 3, which looks as follows (there are, of course, sale and financeCompany models also):
class SaleFinanceCompany < ActiveRecord::Base
attr_accessible :sale_id, :financeCompany_id, :financedBalance
belongs_to :sale
belongs_to :financeCompany
end
My question is simple: how can I set up the sale/financeCompany models so that I can access the associated financedBalance?
For example, in my view I would like to have:
<% for financeCo in sale.financeCompanies %>
<%= "£" + financeCo.financedBalance + " from "%>
<%= financeCo.name %>
<% end %>
That unfortunately does not work, with the error being the financedBalance part. The only way I could see to set up my finance company model would be with a
has_many :financedBalances, :through => :saleFinanceCompanies
but this will give me several financedBalances for each sale, but I need one (each financedBalance is tied to both a sale and finance company in the assignment table, so doing sale.financedBalances.where etc. would seem unnecessary when I should be able to do sale.financeCompany.financedBalance).
Any suggestions?
Rails treats join tables a bit differently than you might think. From a DBA perspective, your join table is perfectly fine but for Rails true join tables have only referential columns. As soon as you add a new column, Rails likes to treat the join table as a new entity.
(On a personal note, I was frustrated by this at first but I quickly learned it's not a big deal)
So, to fix your problem, you'll need to rename your table, let's say FinanceBalances. Also, let's change financedBalance to amount.
Then, in your Sale.rb file put you associations like so:
has_many :financeBalances
has_many :financeCompanies, :through => :financeBalances
Do the same for FinanceCompany.
And your code will look like:
<% for financeBalance in sale.financeBalances %>
<%= "£" + financeBalance.amount + " from " %>
<%= financeBalance.financeCompany.name %>
<% end %>
If you really really want financeCompany.financedBalance to work, you can define a method in your financeCompany model and write the query that returns what you want.
Related
This is a slightly unique version of a polymorphic association. It's one of those "real world" problems that I'm struggling to solve and haven't come across many good answers so I made my own.
A Transaction record has many Tasks and each Task has an Assignee, which can be from multiple tables.
# Models
class Transaction < ApplicationRecord
has_many :tasks
has_many :borrowers
has_many :partners
# Combine into a single array to display on the collection_select
def assignees
borrowers + partners
end
end
class Task < ApplicationRecord
# has attribute :assignee_type_and_id (string)
belongs_to :transaction
# Reverse engineer single attribute into type/id parts
def assignee
if assignee_type_and_id
parts = assignee_type_and_id.split(".")
type = parts.first
id = parts.last
if type.to_s.downcase == "borrower"
Borrower.find(id)
elsif type.to_s.downcase == "partner"
Partner.find(id)
end
end
end
end
class Borrower < ApplicationRecord
# has attribute :name
belongs_to :transaction
def type_and_id
"borrower.#{id}"
end
end
class Partner < ApplicationRecord
# has attribute :name
belongs_to :transaction
def type_and_id
"partner.#{id}"
end
end
On the Task form pages, I want a single HTML select that combines BOTH the Borrowers and Partners.
Classic polymorphism says to add an assignee_type column, but now I'm working with 2 fields instead of one.
My solution is to combine these 2 into a single select such that the final value is of the format assignee_type.assignee_id.
# form.html.erb
<%= form_for #task do |f| %>
<%= f.label :assignee_type_and_id, "Assignee" %>
<%= f.collection_select :assignee_type_and_id, #transaction.assignees, :name, :type_and_id %>
<% end %>
When the form is submitted, it POSTs values in the format borrower.123, partner.57, etc, and that value gets stored in the DB column.
When I want to retrieve the actual task's Assignee, I have to do a little reverse engineering as noted above in the Task#assignee method.
Question
Is there a more appropriate way to do this? I came up with this myself, which scares me because I know problems like this must have been solved by people much smarter than me...
Is there a way to make this work with "normal" polymorphism instead of forcing my own hybrid version?
Update
I happened upon Rails 4.2+ GlobalID, which seems to do this very thing. Unless there's a reason not to use that, I may use that implementation instead of my own "bastardized" version. Is there any better solution to a situation like this?
For these type of problems where a form spans multiple models/complex associations I use a form backing object. It keeps everything clean and modular. Here is a good write up: https://content.pivotal.io/blog/form-backing-objects-for-fun-and-profit
I have a feeling this is a pretty basic question, but for some reason I'm stumped by it (Rails newbie) and can't seem to find the answer (which may be I'm not searching properly).
So I have a basic has_many :through relationship like this:
class User < ApplicationRecord
has_many :contacts, through :user_contacts
class Contact < ApplicationRecord
has_many :users, through :user_contacts
In users/show.html.erb I'm iterating through a single user's contacts, like:
<% #user.contacts.each do |c| %>
<%= c.name %>
<% end %>
Now inside of that each loop, I want to access the user_contact join model that's associated with the given user and contact in order to display the created_at timestamp that indicates when the user <--> contact relationship was made.
I know I could just do a UserContact.find call to look up the model in the database by the user_id and contact_id but somehow this feels superfluous. If I understand correctly how this works (it's entirely possible I don't) the user_contact model should have already been loaded when I loaded the given user and its contacts from the database already. I just don't know how to properly access the correct model. Can someone help with the correct syntax?
Actually the join model will not have been loaded yet: ActiveRecord takes the through specification to build its SQL JOIN statements for querying the correct Contact records but effectively will only instantiate those.
Assuming you have a UserContact model, you could do sth like this:
#user.user_contacts.includes(:contact).find_each do |uc|
# now you can access both join model and contact without additional queries to the DB
end
If you want to keep things readable without cluttering your code with uc.contact.something, you can set up delegations inside the UserContact model that delegate some properties to contact or user respectively. For example this
class UserContact < ActiveRecord::Base
belongs_to :user
belongs_to :contact
delegate :name, to: :contact, prefix: true
end
would allow you to write
uc.contact_name
First of all, the has_many :things, through: :other_things clause is going to look for the other_things relationship to find :things.
Think of it as a method call of sorts with magic built in to make it performant in SQL queries. So by using a through clause you're more or less doing something like:
def contacts
user_contacts.map { |user_contact| user_contact.contacts }.flatten
end
The context of the user_contacts is completely lost.
Since it looks like user_contacts is a one-to-one join. It would be easier to do something like this:
<% #user.user_contacts.each do |user_contact| %>
<%= user_contact.contact.name %>
<% end %>
Also since you're new to Rails it's worth mentioning that to load those records without an N+1 query you can do something like this in your controller:
#user = User.includes(user_contacts: [:contacts]).find(params[:id])
Use .joins and .select in this way:
#contacts = current_user.contacts.joins(user_contacts: :users).select('contacts.*, user_contacts.user_contact_attribute_name as user_contact_attribute_name')
Now, inside #contacts.each do |contact| loop, you can call contact.user_contact_attribute_name.
It looks weird because contact doesn't have that user_contact_attribute_name, only UserContact does, but the .select portion of the query will make that magically available to you on each contact instance.
The contacts.* portion is what tells the query to make all contact's attributes available as well.
I think this is a lot simpler than the title probably lets on. Here are my three models with the associations:
Update: associations were incorrect previously. Here are the corrected associations:
#app/models/call_service.category.rb
class CallServiceCategory < ActiveRecord::Base
has_many :call_services
end
#app/models/call_service.rb
class CallService < ActiveRecord::Base
belongs_to :call_service_category
has_many :calls
end
#app/models/call.rb
class Call < ActiveRecord::Base
belongs_to :call_service
end
So I have a group of call_ids for the calls I want:
#call_ids = [1,2,3,4]
Initial step which works:
What I want to do is grab only the calls with the ids specified in #call_ids. Then, I want to eager load only the associated call_services for those grabbed calls. The following does this perfectly:
#call_ids = [1,2,3,4]
#calls_by_service = CallService.includes(:calls).where("calls.id IN (?)", #call_ids).references(:calls)
This is great. Now I can iterate through only those selected calls' call_services, and I can even list all of those selected calls per service like so:
<% #calls_by_service.each do |call_service| %>
<p> <%= call_service.description %> </p>
<% call_service.calls.each do |call| %>
<%= call.name %><br>
<% end %>
<% end %>
What is great about this too is that #calls_by_service does not contain ALL of the call_services, but instead only those call_service records associated with the calls specified in #call_ids. Exactly what I want at this level.
One Level Deeper which is where I am having trouble:
This is great but I need to go one level deeper: I want to display only the associated call_service_categories for the associated call_services of those selected calls specified by #call_ids.
In other words: I want to grab only the calls with the ids specified in #call_ids. Then: I want to eager load only the associated call_services for those grabbed calls. Then: I want to eager load only the associated call_service_categories for those grabbed calls.
A visual of the structure is like this:
So I want to be able to iterate through those associated call_service_categories (ex: 'Emergency Relief', 'Employment'), and then iterate through the associated call_services of those calls specified in the #call_ids, and then display those calls per service.
I figured out one level (by call_service), now I just need to figure out one level deeper (by call_service_category).
In the rails guides, I attempted looking at the section on specifying conditions on eager loaded associations. I was not having success, but I think the answer is in that section.
Any help is much appreciated. Thanks!
One of the belongs_to associations (in CallService or Call) should be actually a has_one (one-to-one relationship – belongs_to on the one side and has_one on the other). Apart from that, you can try the following code to produce a chained query with left joins and retrieve fields from all 3 tables:
CallServiceCategory.includes(call_services: :calls)
.where(calls: {id: #call_ids})
.references(:call_services, :calls)
I've noticed that you have a through association on your CallServiceCategory model, but as there would be no :call_services in includes, you can't reference fields from CallService model in references (you can, but they just won't appear in the actual sql query).
Under more typical circumstances (ie the objects being edited/created belong to another model) the following would work:
models
Person < AR:Base
has_many :things
end
Thing < AR:Base
belongs_to :person
end
haml
=form_for #person do |f|
=f.fields_for :things do |thing_form|
=thing_form.description
However I need to edit a collection of things (queried from the database .. select * from things where created_at > 2012-01-01) without consideration for the Person they belong to (some don't even belong to a person).
My fields_for /should/ look something like this, but I'm not sure how to set it up before this (as I have no object to build the form from)
...
-#things.each do |thing|
=f.fields_for :thing, thing do |thing_fields|
=thing_form.description
if I could make a 'dummy' person and load the things array with my selection of things, then save it Person without actually saving the dummy, that would work... but how? :)
I have same problem for categories of user. See my solution below.
- Category.all.each do |c|
= check_box_tag "user[category_ids][]", c.id, #user.categories.include?(c), :id => "user_category_ids_#{c.id}"
I am trying to finish building a Rails "Home Inventory" app as an example to help me learn rails. Following gives a general overview of what I am trying to achieve:
The main purpose of this application is to show a page with detailed information.
So, http://localhost:3000/living-room-couch would display the information regarding the couch.
Each Item, can belong one( or has one?) of three categories:
Book
Furniture
Electronics.
Book has the following properties:
- isbn,
- pages,
- address,
- category
Furniture has following properties:
- color,
- price,
- address,
- category
Electronics has following properties:
- name,
- voltage,
- address,
- category.
--
Now on my View side, I have made 3 templates in rails, that contain elements suited to display an item belonging to one of the 3 categories. Template for Book shows isbn, and template for Electronics shows the voltage.
How do I model this in ActiveRecord? I will put it in English, maybe someone can help translate into Rails:
An Item, belongs_to or has_one category. Category can be one of the three: Book, Furniture, or Electronic.
I don't know how to do this. I know that each category like Book will be a model of its own, due to having different characteristics.
Do I need to have Category has a model too, because it would only consist of Book, or Furniture or Electronics. If I was to take the route of making Category a model of its own, how would I make it relate to a model such as Book.
--or
Would I just go this route (or join model perhaps):
class BookModel < ActiveRecord::Base
has_many :categories
End
And then, select to which category belongs, based on the Model name.
I hope I put the question right, I am just so confused regarding this.
Thank You for your time.
Why not have an Item have the following relationships:
belongs_to :book
belongs_to :furniture
belongs_to :electronic
You can then only actually set on of these, and can do a test of
if item.book
#do things
end
for each item, if you care about whether it's a book, etc. You can even use validations to make sure it only ever belongs to one thing at a time.
I don't think you need the category model. You can just create the book, furniture and electronic models and one controller for each one.
Edit
No, there's no advantage. I just said to create different models/controllers because what you're modeling, in my opinion, are different things. But if you think they'll have a lot in common, I'd suggest for you to use single table inheritance. This way you'd have a single table for every item and can have only one controller, but each item would have one model.
the Item model could be declared as polymorphic (se here: http://guides.rails.info/association_basics.html#polymorphic-associations)
so you'll declare Item:
class Item < ActiveRecord::Base
belongs_to :category, :polymorphic => true
end
then the 3 models that act as category (I'll show one, the others are same):
class Employee < ActiveRecord::Base
has_one :item, :as => :category
end
# and so on...
this way you can associate an Item to one of the 3 models (Book, Furniture and Electronics).
each instance of these models will have an 'items' atribute, like this:
#book.items # returns all the items associated to a #book object
You can also use has_many association using a polymorphic model.
For the views, you can use Nested Form Objects (see here: http://guides.rails.info/form_helpers.html). basically, in each form, you should nest a form to create an Item object. to follow the Book example, you'll have something like this:
<%= form_for :book, #book do |form| %>
<%= form.text_field :isbn %>
<!-- other fields -->
<%= fields_for #book.item do |item_form| %>
<%= item_form.text_field :name %>
<% end %>
<% end %>
hope this helped you ;)