Rails: How do I model preferences? Should I use has_many :through? - ruby-on-rails

I have two models: User and HairColor. A user has only one hair color, but can have many hair color preferences. What's the best way to model this?
Here's what I started to do:
#models/user.rb
class User < ActiveRecord::Base
belongs_to :hair_color
has_many :preferences,
has_many :hair_colors, :through => :preferences
end
#models/hair_color.rb
class HairColor < ActiveRecord::Base
has_many :users
end
#models/preference.rb
class Preference < ActiveRecord::Base
belongs_to :user
belongs_to :hair_color
end
Is using has_many :through the right approach? Also what if I want to extend this to other attributes such as "eye color"? (A user has one eye color, but can prefer many eye colors"

There will be a limited amount of hair colors, so the preference table needs to have a hair_color_id and be set up like so:
#models/user.rb
class User < ActiveRecord::Base
has_one :hair_color
has_many :preferences
end
#models/hair_color.rb
class HairColor < ActiveRecord::Base
belongs_to :user
belongs_to :preference
end
#models/preference.rb
class Preference < ActiveRecord::Base
belongs_to :user
has_many :hair_color
end
I believe that's correct. Let me know if you run into any snags.
When you add eye color or any other characteristic, you'll probably have to do something different with preference. I'd have 4 columns at that point: id, user_id, foreign_id, foreign_type
foreign_id would be the id from the eye_color/hair_color table, and foreign_type would be "eye" or "hair" or something. Then in your model, you'd have something like this:
#models/hair_color.rb
class HairColor < ActiveRecord::Base
belongs_to :user
has_many :preferences, :foreign_key => :foreign_id, :conditions => { "preferences.foreign_type" => "hair" }
end
That gets a little crazy, but it's the most DRY way of doing it. You'd put the same thing in your eye_color.rb and just replace "hair" with "eye" for the foreign_type.

Related

How to implement ancestry for this specific situation

We have a Real-Estate app in which the User can be a Renter or Landlord(Owner). Renters can search for specific houses listed by the Owners. Renters can also add other persons(friends or acquaintances who are staying with that specific renter). In the app we treat them as Coapplicants.
Models
# user.rb
class User < ActiveRecord::Base
has_one :renter
has_one :owner
end
# renter.rb
class Renter < ActiveRecord::Base
belongs_to :user
has_many :coapplicants
end
# coapplicant.rb
class Coapplicant < ActiveRecord::Base
belongs_to :renter
end
Now as to increase the number of users for the app,we implemented a mailing system which sends an Welcome Mail(when the Renter adds a Coapplicant) to signup as a User.And that Coapplicant can choose to be Renter and can add many Coapplicants too. And the process goes on again resulting in increasing the users.
It's like a tree structure and now I want to set-up a perfect database relations(associations) to track the users flowing in and through which renter/coapplicant they are coming.
Now the Current Model structure(not yet developed) looks like this
# user.rb
class User < ActiveRecord::Base
has_one :renter
has_one :owner
end
# renter.rb
class Renter < ActiveRecord::Base
belongs_to :user
has_many :coapplicants
has_many :coapp_renters,
:through => :coapplicants
has_many :inverse_coapplicants,
:class_name => "Coapplicant",
:foreign_key => "coapp_renter_id"
has_many :inverse_coapp_renters,
:through => :inverse_coapplicants,
:source => :renter
end
# coapplicant.rb
class Coapplicant < ActiveRecord::Base
belongs_to :renter
belongs_to :coapp_renter,
:class_name => "Renter"
end
I guess i messed up things a bit. Which database-relationships(associations) would be the best for my current situation.
Can someone throw some light on this please.I'm thinking about using the ancestry gem but how to implement to my current situation.
I've found that sometimes even a small change in perspective when designing your associations can make them flow a lot more naturally.
You have focused exclusively on person entities; User.find(1).renter for example isn't very intuitive, since both models depict essentially the same person.
Instead of trying to model what people are, I would try to model what they have. In this case, instead of a User having a Renter, let them have many Rentals:
class User < ActiveRecord::Base
has_many :rentals,
foreign_key: 'renter_id'
end
class Rental
belongs_to :renter,
class_name: 'User'
belongs_to :property
end
I assume here that you have a model Property that stands for what is being rented - leave that out if it doesn't exist.
It's the same thing for owners. A User becomes an owner just by having Ownerships:
class User < ActiveRecord::Base
has_many :ownerships,
foreign_key: 'owner_id'
end
class Ownership
belongs_to :owner,
class_name: 'User'
belongs_to :property
end
A co-application is slightly different in that it belongs to a Rental:
class CoApplication
belongs_to :co_applicant,
class_name: 'User'
belongs_to :rental
end
class Rental
has_many :co_applications
end
class User < ActiveRecord::Base
has_many :co_applications,
foreign_key: 'co_applicant_id'
has_many :received_co_applications,
through: :rentals,
source: :co_applications
end
Now your Users can be owners, renters, co-applicants - all at the same time. And these associations allow you to capture everything that happened - who signed on whom through what is just a matter of chronological order.
From here on it's a matter of nesting your has_many :through associations to get whatever you want.
Want to know the properties a landlord owns?
has_many :owned_properties,
through: :ownerships,
source: :property
The rentals to her properties?
has_many :leases,
through: :owned_properties,
source: :rentals
The people who rented her properties?
has_many :renters,
through: :leases,
source: :renter
Same thing with co-applications. Want to know who co-applied with a user?
has_many :co_applicants,
through: :received_co_applications,
source: :co_applicant
I would refactor your code and make the co-applicants just a renter that is a child of another renter
in your renter model you have to add a "parent_id" to know whom the co-applicants belongs to.
now in your model you can do something like
#renter.rb
class Renter < ActiveRecord::Base
belongs_to :user
has_many :children, :class_name => "Renter"
belongs_to :parent, :class_name => "Renter"
end
# Example calls
Renter.first.children
Renter.first.parent
I hope this helps
Create a table called Relationship (or something) with two foreign_ids concerning what you want the User and Renter to be able to do with one another (example to be able to "Follow" one another => Follower_id and Following_id). Define methods in your Models related to those ids and then call those methods in your views to display the relations.

Use where on has_many :through to find objects with 2 certain child object from different classes (Rails 4)

What I want to do is to get all the
My model:
class User < ActiveRecord::Base
has_many :classes
has_many :professors, :through=>:classes
has_many :cars
has_many :carmodels, :through=>:cars
end
class Professor < ActiveRecord::Base
has_many :classes
has_many :users, :through=>:classes
end
class Class < ActiveRecord::Base
belongs_to :user
belongs_to: professor
end
class Car < ActiveRecord::Base
belongs_to :user
belongs_to :carmodel
end
class Carmodel
has_many :cars
has_many :users, through=>:cars
end
what I want to do is, given a certain Car and Professor, to find all users which contain them.
for example
u1=carmodel.users
u2=professor.users
result=[]
u1.each do |us|
if u2.include? us
result.push us
end
end
Of course this is just an example... I would like to keep working with ActiveRecords(avoid turning it to an array) and, of course, a more optimal solution... I can't seem to find any.
You need to do something like this:
User.joins(:carmodels).joins(:professors)
Thanks for the previous answer! Here is the resulting code:
carmodel #Variable for the wanted carmodel
professor #Variable for the wanted professor
result=User.joins(:carmodels).joins(:professors).where(carmodel:{id:carmodel.id},professors:{id:professor.id})
Thanks again!

Ruby on Rails four-way association tree

The big picture: I am creating an app to track temporal events historically. I am setting it up with four main models: Users, Events, Stories and Images. The idea is that any given user can create an Event, and add Stories and/or Images to his own Event or to any other one. But both Stories and Images are always attached to a certain Event, and any other model belongs to the User who created it (for filtering and editing purposes)
The general structure of the associations would be something like this:
class User < ActiveRecord::Base
has_many :events
has_many :stories
has_many :images
end
class Event < ActiveRecord::Base
belongs_to :user
has_many :stories
has_many :images
end
class Story < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
class Image < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
I am kind of new to Rails and I have little-to-no control over more complex associations as many_to_many, through: or as:
My question here is, is this set of associations optimal, or could it be improved by combining them in a different way?
You're in the right track, but story and image shouldn't belong to user. You're creating a redundancy in there.
class User < ActiveRecord::Base
has_many :events
has_many :stories, :through => :events
has_many :images, :through => :events
end
class Event < ActiveRecord::Base
belongs_to :user
has_many :stories
has_many :images
end
class Story < ActiveRecord::Base
belongs_to :event
end
class Image < ActiveRecord::Base
belongs_to :event
end
This way you can still write user.stories and user.images.
Try to use nested attributes:
http://railscasts.com/episodes/196-nested-model-form-part-1
Sample code:
class User < ActiveRecord::Base
has_many :events
accepts_nested_attributes_for :events
end
class Event < ActiveRecord::Base
belongs_to :user
has_many :stories
has_many :images
accepts_nested_attributes_for :stories, :images
end
class Story < ActiveRecord::Base
belongs_to :event
end
class Image < ActiveRecord::Base
belongs_to :event
end
Nested attributes accepts many level. In this case, there are 3 levels
user
event
story and image.
It like the sample at rails cast with 3 level (survey - question -answer).
Look at the link above and watch the part 2 to see how it work.
Or you can't look at my sample, but it work with 2 levels(subject - task).
Link at here
Login with account: duyet.vn#gmail.com/12341234

activerecord / db theory - where do I put these fields?

I have the models shown below. I need to store some details that are specific a person and a house (first_viewed:date, opening offer:decimal, etc). I feel like these should belong to the PersonHouse model but I'm not certain enough. Any suggestions?
class Person < ActiveRecord::Base
has_many :houses, through: :person_houses
has_one :favorite_house, through: :person_houses
end
class PersonHouse < ActiveRecord::Base
belongs_to :house
belongs_to :person
end
class House < ActiveRecord::Base
has_many :house_people
has_many :people, through: :person_houses
end
I could do something like this to get all the details but perhaps there is a more effient way.
#house = House.find(1)
#house.house_people.each do |hp|
puts hp.person.name
puts hp.first_viewed
puts #house.address
end
I think your assumption is correct. If the data is relevant to the relationship between a person and a house, then yes it belongs on this model. The only recommendation I would make is to rename this model to a name that better describes what the relationship is. It doesn't have to be the concatenation of the two models it joins. I don't know exactly what the model is going to be ultimately used for, but SelectedHouse, HouseProspect or something along those lines might work.
You can also delegate properties to the house or person models:
class PersonHouse < AR::Base
belongs_to :person
belongs_to :house
delegate :address, :to => :house, :prefix => true
delegate :name, :to => :person, :prefix => true
end
person_house.address
person_house.person_name

Can a 3-way relationship be modeled this way in Rails?

A User can have many roles, but only one role per Brand.
Class User < AR::Base
has_and_belongs_to_many :roles, :join_table => "user_brand_roles"
has_and_belongs_to_many :brands, :join_table => "user_brand_roles"
end
The problem with this setup is, how do I check the brand and the role at the same time?
Or would I better off with a BrandRole model where different roles can be set up for each Brand, and then be able to assign a user to a BrandRole?
Class User < AR::Base
has_many :user_brand_roles
has_many :brand_roles, :through => :user_brand_roles
end
Class BrandRole < AR::Base
belongs_to :brand
belongs_to :role
end
Class UserBrandRole < AR::Base
belongs_to :brand_role
belongs_to :user
end
This way I could do a find on the brand for the user:
br = current_user.brand_roles.where(:brand_id => #brand.id).includes(:brand_role)
if br.blank? or br.role != ADMIN
# reject access, redirect
end
This is a new application and I'm trying to learn from past mistakes and stick to the Rails Way. Am I making any bad assumptions or design decisions here?
Assuming Roles,Brands are reference tables. You can have a single association table Responsibilities with columns user_id, role_id, brand_id.
Then you can define
Class User < AR::Base
has_many : responsibilities
has_many :roles, :through => responsibilities
has_many :brands,:through => responsibilities
end
Class Responsibility < AR::Base
belongs_to :user
has_one :role
has_one :brand
end
The you can define
Class User < AR::Base
def has_access?(brand)
responsibility = responsibilities.where(:brand => brand)
responsibility and responsibility.role == ADMIN
end
end
[Not sure if Responsibility is the term used in your domain, but use a domain term instead of calling it as user_brand_role]
This is a conceptual thing. If BrandRole is an entity for your application, then your approach should work. If BrandRole is not an entity by itself in your app, then maybe you can create a UserBrandRole model:
class User < AR::Base
has_many :user_brand_roles
end
class Brand < AR::Base
has_many :user_brand_roles
end
class Role < AR::Base
has_many :user_brand_roles
end
class UserBrandRole < AR::Base
belongs_to :user
belongs_to :brand
belongs_to :role
validates_uniqueness_of :role_id, :scope => [:user_id, :brand_id]
end

Resources