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.
Related
I need to know, if there is a way to deal with different type of users/clients in Rails using a single model.
What I really need to do?
- I need to save a different type of clients in my database. So, I have this migration:
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.string :name # Person name if "Personal", Company name if type "Company"
t.string :nif # fillable only if type is Company
t.string :identity_card_number # fillable only if type is Personal
t.integer :client_type # Type can be Personal or Company
t.references :company, index: true # if type is personal, it can belong to a company
t.timestamps null: false
end
end
end
Then I create this model
class Client < ActiveRecord::Base
has_many :employees, class_name: 'Client', foreign_key: 'company_id'
belongs_to :company, class_name: 'Client'
end
Note: A personal account can belong to a company or not.
Based on your experience, am I doing this in the right way? There are another way to do that?
EDIT:
Hi #manfergo25,
Whit this I have another question. "Company" and "Personal" are both "Clients Account", in that way, both must be able to buy services.
If I need to associent the client with the service, can I do this?
class Personal < Account
has_many :services
end
and
class Service < ...
belongs_to :account
end
??
The right way is Single Table Inheritance (STI) as Sontya say.
class Account < ActiveRecord::Base
end
Then,
class Client < Account
end
class Provider < Account
end
You only have to add a type column in 'Account' to contain a string representing the type of the stored object.
For example in a controller you could do this:
account = Client.find(params[:autocomplete_client])
params[:service][:account_id] = account.id
#service = Service.new(params[:service])
You can use STI(Single Table Inheritance)
class Account < ActiveRecord::Base
end
class Company < Account
has_many :services, :dependent => :destroy
end
class Personal < Account
has_many :services, :dependent => :destroy
end
class Service < ActiveRecord::Base
belongs_to: personal
belongs_to: company
end
With the above definition, a personal and company should be able to buy services.
and you should be able to call
#company.services # it will return you the number of services of company
#personal.services # it will return you the number of services of personal
Personally, I think the way you have suggested in your question is the way I'd do it.
While Single Table Inheritance is intellectually better modelling -- I've found STI to be a bit hard to work with and sometimes unreliable in Rails. And probably won't improve the cleanliness or conciseness of your code all that much in the end anyway. STI is good to keep in mind as an option, if you find the OP approach is not working well as far as allowing you to write clear concise code and it seems STI could work out better.
But I'd start without STI, with just the one Client class, as you've outlined in your question. If you later add STI, you'd still have the Client class, you'd just have sub-classes for, say, PersonalClient and CompanyClient. It won't be that hard to switch to STI later if you want to, although it might require some minor db schema alterations.
But I don't think it'll get you enough benefit to justify the added complexity, in an area of Rails that has sometimes had some rough edges.
Here's some more info about STI and it's plusses and minuses: http://eewang.github.io/blog/2013/03/12/how-and-when-to-use-single-table-inheritance-in-rails/
I have a question about rails and how its relationships query builder, specifically how camel case is converted for the related calls.
Relevant Code
class CustomerPlan < ActiveRecord::Base
attr_accessible :customer_id, :plan_id, :startDate, :user_id
has_many :planActions
end
class PlanAction < ActiveRecord::Base
attr_accessible :actionType_id, :customerPlan_id, :notes, :timeSpent
belongs_to :customerPlan
belongs_to :actionType
end
The getters and setters work just fine, such as plan_action.actionType.name will correctly pull from the related model. However customer_plan.planActions.each returns the error:
SQLite3::SQLException: no such column: plan_actions.customer_plan_id:
SELECT "plan_actions".*
FROM "plan_actions"
WHERE "plan_actions"."customer_plan_id" = 1
The column is defined in the database as customerPlan_id, was I just wrong to use this? It works for every other call, all my other relationships work fine. Even PlanAction -> CustomerPlan.
I ran through all the docs, and searched about every other source I know of. It would be simple enough to change my columns, I just want to know what's going on here.
Thank you for your time!
A quick fix for this is to just explicitly set the foreign_key.
has_many :planActions, :foreign_key => "customerPlan_id", :class_name => "PlanAction"
Still, I think I am missing some model naming convention somewhere, just can't seem to figure out what.
The Rails convention for DB column names is to use lowercase letters with words separated by an underscore (e.g. author_id, comments_count, updated_at, etc).
I would highly recommend that you stick to the Rails conventions. This would make your life much easier. To change it to the rails convention, simply create a migration to rename the column to the appropriate style.
However, if you do want to use a custom style for the column name, rails provides the :foreign_key option in the has_many relationship to specify the expected foreign column name:
class CustomerPlan < ActiveRecord::Base
has_many :plan_actions, :foreign_key => 'customerPlan_id'
end
You can also use the alias_attribute macro to alias the column name, if you'd like to use a different model attribute name than the actual DB column name. But as I mentioned, I would recommend sticking to the rails convention as much as possible. You'll thank me later.
Rails has 3 basic naming schemes.
One is for constants, and it is ALL_UPPERCASE_SEPARATED_BY_UNDERSCORES.
One is for Classes and it is AllCamelCaseWithNoUnderscores.
One is for variables and method names, and is all_lowercase_separated_by_underscores.
The reason that it is this way is not just for consistency, but also because it freely converts between them using these methods.
So, to make your posted code more rails-y:
class CustomerPlan < ActiveRecord::Base
attr_accessible :customer_id, :plan_id, :start_date, :user_id
has_many :plan_actions
end
class PlanAction < ActiveRecord::Base
attr_accessible :action_type_id, :customer_plan_id, :notes, :time_spent
belongs_to :customer_plan
belongs_to :action_type
end
I've got some models (Rails 3.2) like this, A->B->C, to model a Manager (A) who covers many Areas (B), each of which has many Addresses (C). One of those addresses is the manager's primary address. I can't figure out the best way to associate a Manager with his primary address.
class AreaManager < ActiveRecord::Base
has_many :areas, :dependent=>:destroy
has_many :addresses, :through=>:areas
...
def primary_address
self.addresses.where(:primary=>true).first
# in fact, there can be only one
end
end
class Area < ActiveRecord::Base
belongs_to :area_manager
has_many :addresses,:dependent=>:destroy
end
class Address < ActiveRecord::Base
belongs_to :area
attr_accessible :primary
# this is the manager's primary address (not the area's)
end
This allows me to say, for example:
a = AreaManager.find(123)
a.primary_address # => her primary address
But what I really want is a constructor/setter as well as a getter:
a = AreaManager.new
a.build_primary_address ...
I can add to Area:
has_one :primary_address,
:class_name=>'Address',
:conditions => {:primary=>true}
and this allows me to do
a1 = Area.new
a1.build_primary_address ... # instantiates an Address with primary = true
but when I try to do something similar to AreaManager, by adding
has_one :primary_address,
:through=>:areas,
:source=>:address,
:conditions=>{:primary=>true}
and then say:
a = AreaManager.new
a.build_primary_address ...
# instead of creating an area and an address, it croaks
I get a ActiveRecord::HasOneThroughCantAssociateThroughCollection exception. I can't figure out what the right incantation might be (or if there is one). Is there a Railsy way to do this?
As rails is all about following conventions and the right chocie from many options, I would recommend:
Don't apply conditions to an association
Look to use a named scope
Look to use .where (rails3) instead of conditions (rails2). See the ActiveRecord api for more
Applying all this will likely change your code a fair bit and you'll get different results.
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!!
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