Multiple foreign keys referencing the same table in RoR - ruby-on-rails

I want a Customer to reference two Address models, one for the billing address and one for the shipping address. As I understand it, the foreign key is determined by its name, as _id. Obviously I can't name two rows address_id (to reference the Address table). How would I do this?
create_table :customers do |t|
t.integer :address_id
t.integer :address_id_1 # how do i make this reference addresses table?
# other attributes not shown
end

This can be kind of confusing to people new to Rails (as I was recently), because some parts of the answer take place in your Migrations and some in your Models. Also, you actually want to model two separate things:
An address belongs to a single customer and each customer has many addresses. In your case this would be either 1 or 2 addresses, but I would encourage you to consider the possibility that a customer can have more than one shipping address. As an example, I have 3 separate shipping addresses with Amazon.com.
Separately, we want to model the fact that each customer has a billing address and a shipping address, which might instead be the default shipping address if you allow more than one shipping address.
Here's how you would do that:
Migrations
class CreateCustomers < ActiveRecord::Migration
create_table :customers do |t|
def up
t.references :billing_address
t.references :shipping_address
end
end
end
Here you are specifying that there are two columns in this table that will be referred to as :billing_address and :shipping_address and which hold references to another table. Rails will actually create columns called 'billing_address_id' and 'shipping_address_id' for you. In our case they will each reference rows in the Addresses table, but we specify that in the models, not in the migrations.
class CreateAddresses < ActiveRecord::Migration
create_table :addresses do |t|
def up
t.references :customer
end
end
end
Here you are also creating a column that references another table, but you are omitting the "_id" at the end. Rails will take care of that for you because it sees that you have a table, 'customers', that matches the column name (it knows about plurality).
The reason we added "_id" to the Customers migration is because we don't have a "billing_addresses" or "shipping_addresses" table, so we need to manually specify the entire column name.
Models
class Customer < ActiveRecord::Base
belongs_to :billing_address, :class_name => 'Address'
belongs_to :shipping_address, :class_name => 'Address'
has_many :addresses
end
Here you are creating a property on the Customer model named :billing_address, then specifying that this property is related to the Address class. Rails, seeing the 'belongs_to', will look for a column in the customers table called 'billing_address_id', which we defined above, and use that column to store the foreign key. Then you're doing the exact same thing for the shipping address.
This will allow you to access your Billing Address and Shipping Address, both instances of the Address model, through an instance of the Customer model, like this:
#customer.billing_address # Returns an instance of the Address model
#customer.shipping_address.street1 # Returns a string, as you would expect
As a side note: the 'belongs_to' nomenclature is kind of confusing in this case, since the Addresses belong to the Customers, not the other way around. Ignore your intuition though; the 'belongs_to' is used on whichever thing contains the foreign key which, in our case, as you will see, is both models. Hah! how's that for confusing?
Finally, we are specifying that a Customer has many addresses. In this case, we don't need to specify the class name this property is related to because Rails is smart enough to see that we have a model with a matching name: 'Address', which we'll get to in a second. This allows us to get a list of all of Customer's addresses by doing the following:
#customer.addresses
This will return an array of instances of the Address model, regardless of whether they are billing or shipping addresses. Speaking of the Address model, here's what that looks like:
class Address < ActiveRecord::Base
belongs_to :customer
end
Here you're accomplishing the exact same thing as with the 'belongs_to' lines in the Customer model, except that Rails does some magic for you; looking at the property name ('customer'), it sees the 'belongs_to' and assumes that this property references the model with the same name ('Customer') and that there is a matching column on the addresses table ('customer_id').
This allows us to access the Customer that an Address belongs to like this:
#address.customer # Returns an instance of the Customer model
#address.customer.first_name # Returns a string, as you would expect

This sounds like a has_many relationship to me - put the customer_id in the Address table instead.
Customer
has_many :addresses
Address
belongs_to :customer
You can also provide a foreign key and class in the assoc declaration
Customer
has_one :address
has_one :other_address, foreign_key => "address_id_2", class_name => "Address"

I figured out how to do it thanks to Toby:
class Address < ActiveRecord::Base
has_many :customers
end
class Customer < ActiveRecord::Base
belongs_to :billing_address, :class_name => 'Address', :foreign_key => 'billing_address_id'
belongs_to :shipping_address, :class_name => 'Address', :foreign_key => 'shipping_address_id'
end
The customers table includes shipping_address_id and billing_address_id columns.
This is essentially a has_two relationship. I found this thread helpful as well.

In Rails 5.1 or greater you can do it like this:
Migration
create_table(:customers) do |t|
t.references :address, foreign_key: true
t.references :address1, foreign_key: { to_table: 'addresses' }
end
This will create the fields address_id, and address1_id and make the database level references to the addresses table
Models
class Customer < ActiveRecord::Base
belongs_to :address
belongs_to :address1, class_name: "Address"
end
class Address < ActiveRecord::Base
has_many :customers,
has_many :other_customers, class_name: "Customer", foreign_key: "address1_id"
end
FactoryBot
If you uses FactoryBot then your factory might look something like this:
FactoryBot.define do
factory :customer do
address
association :address1, factory: :address
end
end

I had the same problem and solved doing this:
create_table :customers do |t|
t.integer :address_id, :references => "address"
t.integer :address_id_1, :references => "address"
# other attributes not shown
end

Related

Rails Composite Keys - Setting up models and assocs

I'm using Rails Composite Keys for the first time and even though I've read the docs, I'm still unsure how to set up the models correctly.
An Activity can be Scheduled by a Provider. I want a join table that tracks activity_id and provider_id as a unique composite key so that I can associate it with something else (prices). The composite needs to be generated upon the creation of a new schedule. The provider does not necessarily own the activity themselves directly.
I've got this for my composite key as it's own model. Is this right?
class ScheduledActivity < ActiveRecord::Base
self.primary_keys = :provider_id, :activity_id
belongs_to :activity
belongs_to :provider
has_many :schedules, :foreign_key => [:provider_id, :activity_id]
has_many :prices, :foreign_key => [:provider_id, :activity_id]
end
This for the db migration:
class CreateJoinTableScheduledActivities < ActiveRecord::Migration
def change
create_join_table :providers, :activities, table_name: :scheduled_activities do |t|
t.index [:provider_id, :activity_id]
end
end
end
How do I then get new entries on the join table as Schedules are created? Do I put only belongs_to :scheduled_activity on this model - i.e. no provider and activity ids on the Schedule table, and do I write a separate hook in order to create the new composite keys?
(Also - is this the correct use case for composite keys in the first place?!)
Thanks in advance!
Well I'm not sure if this fully answers your question but if you have a composite unique key you can do something like the following.
In your migration:
add_index :scheduled_activities, [:provider_id, :activity_id], unique: true
In your join model:
belongs_to :activity
belongs_to :provider
validates :activity, presence: true, uniqueness: { scope: :provider }
Given that this is more than a simple join table maybe you should consider using id's to identify each ScheduledActivity, then you can simply add that reference to your Schedule model/table (Schedule belongs_to :schedule_activity - schedules table will have a reference to schedule_activity with attribute schedule_activity_id, instead of using a composite key). It would make things much simpler and efficient.

Rails: Address model being used twice, should it be separated into two tables?

I am making an ecommerce site, and I have Purchases which has_one :shipping_address and has_one :billing_address
In the past the way I've implemented this is to structure my models like so:
class Address < ActiveRecord::Base
belongs_to :billed_purchase, class_name: Purchase, foreign_key: "billed_purchase_id"
belongs_to :shipped_purchase, class_name: Purchase, foreign_key: "shipped_purchase_id"
belongs_to :state
end
class Purchase < ActiveRecord::Base
INCOMPLETE = 'Incomplete'
belongs_to :user
has_one :shipping_address, class: Address, foreign_key: "shipped_purchase_id"
has_one :billing_address, class: Address, foreign_key: "billed_purchase_id"
...
end
As you can see, I reuse the Address model and just mask it as something else by using different foreign keys.
This works completely find, but is there a cleaner way to do this? Should I be using concerns? I'm sure the behavior of these two models will always be 100% the same, so I'm not sure if splitting them up into two tables is the way to go. Thanks for your tips.
EDIT The original version of this was wrong. I have corrected it and added a note to the bottom.
You probably shouldn't split it into two models unless you have some other compelling reason to do so. One thing you might consider, though, is making the Address model polymorphic. Like this:
First: Remove the specific foreign keys from addresses and add polymorphic type and id columns in a migration:
remove_column :addresses, :shipping_purchase_id
remove_column :addresses, :billing_purchase_id
add_column :addresses, :addressable_type, :string
add_column :addresses, :addressable_id, :integer
add_column :addresses, :address_type, :string
add_index :addresses, [:addressable_type, :addressable_id]
add_index :addresses, :address_type
Second: Remove the associations from the Address model and add a polymorphic association instead:
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
...
end
Third: Define associations to it from the Purchase model:
class Purchase < ActiveRecord::Base
has_one :billing_address, -> { where(address_type: "billing") }, as: :addressable, class_name: "Address"
has_one :shipping_address, -> { where(address_type: "shipping") }, as: :addressable, class_name: "Address"
end
Now you can work with them like this:
p = Purchase.new
p.build_billing_address(city: "Phoenix", state: "AZ")
p.build_shipping_address(city: "Indianapolis", state: "IN")
p.save!
...
p = Purchase.where(...)
p.billing_address
p.shipping_address
In your controllers and views this will work just like what you have now except that you access the Purchase for an Address by calling address.addressable instead of address.billed_purchase or address.shipped_purchase.
You can now add additional address joins to Purchase or to any other model just by defining the association with the :as option, so it is very flexible without model changes.
There are some disadvantages to polymorphic associations. Most importantly, you can't eager fetch from the Address side in the above setup:
Address.where(...).includes(:addressable) # <= This will fail with an error
But you can still do it from the Purchase side, which is almost certainly where you'd need it anyway.
You can read up on polymorphic associations here: Active Record Association Guide.
EDIT NOTE: In the original version of this, I neglected to add the address_type discriminator column. This is pernicious because it would seem like it is working, but you'd get the wrong address records back after the fact. When you use polymorphic associations, and you want to associate the model to another model in more than one way, you need a third "discriminator" column to keep track of which one is which. Sorry for the mixup!
In addtion to #gwcoffey 's answer.
Another option would be using Single Table Inhertinace which perhaps suits more for that case, because every address has a mostly similar format.

Rails ActiveRecord - How do I determine the owner in a polymorphic relation?

I have a model that could be owned by many other models (It has many foreign keys).
I'm going to try to make a polymorphic function on this model, that will behave depending on who it's owner is. Unfortunately I'm not sure what the active record code would be to find that out, and when I go in binding.pry the self object doesn't have any information I can tell.
So a good example would be Company and Person both have a Tax ID
When the Tax ID model is going to do something, it wants to know who it's owner is. Makes sense?
My actual relationship is a has_many, but I doubt that is the sticking point.
Assuming the following structure,
class Tax
belongs_to :taxable, polymorphic: true
end
class Company
has_many :taxes, as: :taxable
end
class Person
has_many :taxes, as: :taxable
end
create_table :taxes do |t|
t.integer :taxable_id
t.string :taxable_type
t.timestamps
end
each tax record can access its owner using tax.taxable. To get the type, use either
tax.taxable.class.name
or
tax.taxable_type
(With help from #SteveTurczyn and #MrYoshiji.)
class Tax
belongs_to :taxable, :polymorphic => true # tax table needs taxable_type taxable_id
end
class Company
has_one :tax, :as => :taxable
end
class Person
has_one :tax, :as => :taxable
end
Tax.first.taxable

Ruby On Rails User Model for multiple types

I am learning RoR coming from many years of c# and MSSQL.
I have picked a project to build a web site for my brother who is a rental property manager. I figured this should be fairly easy as the models should be straight forward, but it think I may be over thinking everything or I’m having trouble letting go of the ‘old’ way. Anyway here is the problem. I am starting off with just two models (User and Property) . The property model is easy, the user not so much. I figured that we have three types of users in the system. Tenants, Owners and Managers (my brother will be the only manager but I figured I would design it to grow) He manages properties for several owners each of whom can own many properties. Each property will have one owner, one tenant and one manger.
Tenants will be able to log in and just see the property they rent to maybe fill out a maintenance request or something like that…(no real requirement at this point to even give tenant a login to the system but I thought it would be a good exercise)
Same thing goes for owners, none of them really need access to the system (they hire my brother so they don’t have to be involved) but I thought it might be nice and again a good exercise.
I used the Nifty_generator to generate a user, which just gives email, password etc. I have extended it as follows…
class AddProfileDataToUsers < ActiveRecord::Migration
def self.up
add_column :users, :first_name, :string
add_column :users, :last_name, :string
add_column :users, :address1, :string
add_column :users, :address2, :string
add_column :users, :city,:string
add_column :users, :state, :string
add_column :users, :zip, :string
add_column :users, :phone, :string
add_column :users, :email, :string
add_column :users, :user_type, integer
end
def self.down
remove_column :users, :first_name
remove_column :users, :last_name
remove_column :users, :address1
remove_column :users, :address2
remove_column :users, :city
remove_column :users, :state
remove_column :users, :zip
remove_column :users, :phone
remove_column :users, :email
remove_column :users, :user_type
end
end
Here is the code to create the Properties table
class CreateProperties < ActiveRecord::Migration
def self.up
create_table :properties do |t|
t.string :address
t.string :city
t.string :type
t.integer :beds
t.float :baths
t.float :price
t.float :deposit
t.string :terms
t.string :laundry
t.datetime :date_available
t.integer :sqft
t.integer :owner_id
t.integer :manager_id
t.integer :tenant_id
t.timestamps
end
end
def self.down
drop_table :properties
end
end
I added the following to the user model that was generated by the nifty_authentication generator
class User < ActiveRecord::Base
#other stuff in the user model up here......
validates_length_of :password, :minimum => 4, :allow_blank => true
#this is the stuff that I have added to the user model
has_many :managed_properties, :class_name => "Property", :foreign_key => "manager_id"
has_many :owned_properties, :class_name => "Property", :foreign_key => "owner_id"
has_one :rented_property, :class_name => "Property", :foreign_key => "tenant_id"
I then added this to the property model....
class Property < ActiveRecord::Base
belongs_to :manager, :class_name => "User" #picked up by the manager_id
belongs_to :owner, :class_name => "User" #picked up by the owner_id
belongs_to :tenant, :class_name => "User" #picked up by the tenant_id
end
My question is, does this look like an acceptable way of modeling the situation I described?
Should I be using the single table inheritance and creating a tenant model; a manager model; and an owner model? The problem I saw with doing that was that a single user could be both a manager and an owner. This could be solved by then having a roles tables for the user where a user has many roles and a role has many users. I had also looked at a profile table with a one to one match with the user table and making this polymorphic but I didn't think that this situation really calls for that and it didn't solve the issue where a user can be an owner and a manager.....
This is when I started to think that maybe I was over thinking the problem and came up with what you see here.
I welcome any constructive comments you may have. Please keep in mind that I have never actually built anything in Rails and this is all a first attempt, one week ago I had never even installed rails on my computer.
I don't know if this matters but I figured that the admin / manager would be responsible for creating users. This will not be a self sign up type of site. The manager will add new owners when he signs up new owner, and the same will go for tenants. This will make it easier to determine the type of user he is creating.
Thanks for any insight you may have.
FWIW, this looks fine to me. I might look at declarative_authorization to manage your roles, which might end up somewhat involved, particularly from the UI standpoint. Managing users with multiple roles seems more appropriate than STI in this situation, because, as you say, a user can be both manager and tenant, etc.
One approach to keeping the code for manager, tenant and owner separate, while allowing for the use of all three roles in a single instance, might be to dynamically include modules representing those roles based on what roles any given user has. You'd still have a base class of User, but instead of using STI, which would limit you to a single inheritance structure, you could mix in modules based on the roles each user has, which might simply pivot on what Properties belong_to a given user during initialization.
For this I might create a User factory that can inspect the user for the various roles it plays and extend the singleton class of that user with the corresponding module: extend with the Tenant module for users that have tenant properties, extend with the Manager module for users that have managed properties, etc.
From the standpoint of declarative_authorization, you could declare the role_symbols similarly based on whether associations like managed_properties were populated, for instance:
def role_symbols
#roles ||= {:manager => :managed_properties,
:tenant => :rented_property,
:owner => :owned_properties}.map do |k,v|
k if !send(v).blank?
end.compact
end
Or something similar. You'd probably want to set the roles AND include the corresponding module at the same time. Ruby and Rails gives you many options via metaprogramming techniques to dynamically decorate with the functionality individual models need. My approach may or may not be appropriate for your application, but there are countless other ways you could approach the problem and keep your code clean and DRY.
Overall it seems to me your data model is sound, and your instinct not to use STI to manage multiple roles is correct. It doesn't look to me like you've overthought it -- I think you're on the right track. It's actually pretty Rails-y for a first pass.
EDIT: You know, the more I think about it, I'm not sure what the benefit of keeping the manager/tenant/owner functionality in separate modules really is. In my former incarnation as a Java/C# guy I would have been all about the SRP/IOC and total separation of concerns. But in Ruby and Rails it's not nearly as big of a deal, since it's dynamically typed and coupling is not nearly as big, or at least the same sort, of concern that it is in statically-typed environments. You might be perfectly fine just putting all of the individual role functionality in the single User model and not worry with the modules, at least not yet.
I'm on the fence here, and would welcome input from others. To me one of the benefits to Ruby classes, as opposed to Java classes/packages or .NET classes/assemblies, is that you can always refactor as needed and not be nearly as concerned about which class, package, namespace, dll or jar is coupled to another, etc. I'm not saying SRP isn't important in Ruby, not at all. But I'm not nearly as paranoid about it as I used to be.
EDIT: Paul Russell makes an excellent point. I think you should seriously consider allowing multiple tenants/managers/landlords per property. In Rails this could be expressed through a relational table and a has_many :through association, plus STI to describe the different types of relationships. I also think it will be necessary to invert the relationship between User (as Tenant) and Property. A Property can have more than one Tenant, but a Tenant can't live at more than one property. (or maybe they can? Doesn't seem right, but...)
Maybe something like (this is very quick and dirty, so forgive any missed details please):
class PropertyRole < ActiveRecord::Base
belongs_to :user
belongs_to :property
end
class Managership < PropertyRole
# Manager functionality here
end
class Ownership < PropertyRole
# Owner functionality here
end
class User < ActiveRecord::Base
belongs_to :residence, :class_name => 'Property', :foreign_key => 'residence_id'
has_many :managerships
has_many :ownerships
has_many :owned_properties, :through => :ownerships, :classname => 'Property'
has_many :managed_properties, :through => :managerships, :classname => 'Property'
end
class Property < ActiveRecord::Base
has_many :tenants, :class_name => 'User', :foreign_key => 'residence_id'
has_many :managerships
has_many :ownerships
has_many :owners, :class_name => 'User', :through => :ownerships
has_many :managers, :class_name => 'User', :through => :managerships
end
Like I said, this is quick and dirty, a high-level first pass. Note that we've now created Managership and Ownership roles that can contain Manager and Owner-specific functionality, eliminating the former dilemma as to whether to silo that functionality in separate modules.
** Note also that I've inverted the Tenant/Property role as well -- I think this was a necessary change to your domain. Obviously a residence can have more than one tenant. It seems to me (at the moment) that you can keep the tenant-specific functionality on the User model.
What does strike me is that your current model prohibits having multiple tenants or landlords at a given property, and assumes that these relationships are permanent.
In the UK at least, it's very common to have the situation where a property is owned by a married couple, and therefore could have multiple landlords (my wife and I are in this position, for example). Equally, for certain types of letting, it's very common for there to be multiple tenants at a single property.
If you think I'm right here, it's worth considering introducing a 'user_property_role' model object between the user and the property. You'd then have a has_many relationship from both user and property to user_property_role. If this role had a 'relationship type' field that you could set to e.g. 'landlord', you could then use has_many :through and named scopes (on the user_property_role) object to do e.g. property.users.landlords or property.users.tenants.
Taking this approach would also allow you do to things like give these relationships 'begin' and 'end' dates, recording the fact that a property has multiple tenants over time, for example, or that a property might be managed by different people over time. Again, you should be able to build this into a set of named scopes so that you can do e.g. property.users.current.tenants or even user.properties.current.tenants.
Hope that helps, and doesn't just add to your confusion!!

How best to associate an Address to multiple models in rails?

This question on SO appears to be related to my question, but I'm not sure my question is answered by that.
An address can belong to more than one model (UserProfile and Event)
What's the correct way to implement this?
The basic tables:
user_profiles(id)
events(id)
Options for implementing the addresses table:
addresses(id,user_profile_id,event_id)
This approach seems to be kludgy since if tomorrow the address needs to belong to one more model, I have to add that id field.
Also, I don't know yet, but adding a new id field may cause some code
to break as well ?
addresses(id,model_type,model_id)
This is polymorphic, right. I'm don't know why, but I feel wary of this somehow?
Some other way to do this?
Note:
I could have made the tables like this, I suppose:
user_profiles(id,address_id)
events(id,address_id)
But, this means that the same address_id can belong to different models.
I suppose it should not be that way, because say for example that the address for the event needs to be changed, but it should not affect the address of the user_profile.
So that would be something like this (which I think is wrong):
#current_user_profile.address = some_new_address
#this would have changed the address for both the user_profile *and* the event
#current_user_profile.save
One way would be standard Rails polymorphism:
class Address
belongs_to :addressable, :polymorphic => true
end
class UserProfile
has_one address, :as => :addressable
end
class Event
has_one address, :as => :addressable
end
It could be the nagging feeling you have about this is that you can't create a db-level constraint with Rails-style polymorphic relationships. The alternative (suggested by Dan Chak in Enterprise Rails) is like your #1 option, where you do indeed create a separate id field for each type of relationship. This does leave unused fields, but it also allows for constraints. I can see the argument for both, but the Rails community has been using AR polymorphism for some time with apparently good success. I use it without hesitation. But if that bugs you, you can use Chak's approach. It's a lot more work. : )
EDIT: #Slick86, migration would look something like:
class CreateAddresses < ActiveRecord::Migration
def change
create_table :addresses do |t|
t.integer :addressable_id
t.string :addressable_type
end
end
end
You missed one option: have a class that contains the common behavior and add the fields to all tables. Use a composed_of aggregate to manage the data.
class Address
attr_accessor :line1, :line2, :city, :state, :zip
def initialize(line1, line2, city, state, zip)
#line1 = line1
end
end
class UserProfile < ActiveRecord::Base
composed_of :address, :mapping => %w(line1 line2 city state zip)
end
class Event < ActiveRecord::Base
composed_of :address, :mapping => %w(line1 line2 city state zip)
end
See #composed_of in the Ruby on Rails API docs.

Resources