1 user model with student,tutor role + different profile for student, tutor? - activerecord

How to correctly setup a User model with 2 roles, and have 2 separate profile models for each of the roles? Im confused on how to implement. Currently im using this but it fails:
models/user.rb
# id :integer ( only important columns noted to save space)
# profile_id :integer
# profile_type :string(255)
belongs_to :profile, :polymorphic => true
models/profile_student.rb:
# user_id :integer
has_one :user, as: :profile, dependent: :destroy
models/profile_tutor.rb:
# user_id :integer
has_one :user, as: :profile, dependent: :destroy
How to correctly get the profile for a user??
for example using devise.
#user = current_user.profile

I would try having two types of users: student and tutor. In order to do this, in your user table, have a column called type and put in a validation that ensures it is either student or tutor:
validates :type, :inclusion => {:in => ['student', 'tutor']}
Then create a Student model and a Tutor model. In rails, 'type' is a special kind of attribute in which rails will know that it is referring to other models. Then, in order to make profiles, you have two options. You can either say that both a student and a tutor has_one :profile, or you can separate the types of profiles.
For example, you can do:
class Student < User
has_one :profile
end
class Tutor < User
has_one :profile
end
If both profiles have similar types of information, that may work for you. However, if a tutor and student have considerably different profiles, try something like this:
class Student < User
has_one :student_profile
end
class Tutor < User
has_one :tutor_profile
end
and then create a separate model for each type of profile.
By using this 'type' column, you can make it so that students and tutors inherit all the methods and properties of users, but can also have their own distinct properties and methods.

Related

Configuring the proper join column in Rails Admin

I have two models, which associate with each other through a has_and_belongs_to_many relationship.
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands
end
Assume both tables have an id field, and that stage has a stage_name field.
They're related to each other through a table called bands_stages, with a schema that looks similar to this:
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
My intention is to use Rails Admin to allow us to modify certain fields on the Stage, but every time that runs, I get an SQL error doing so:
column stages.id does not exist
It seems that Rails Admin is picking the wrong column by default to join on. How would I inform Rails Admin that I want it to join on a column that actually exists in my join table?
Note that I can't actually make use of the ID in the stages table. The intention is that only ten stages exist at any given time, denoted by their stage number, but every band can visit each stage. Since an ID would automatically increment, it seems to be safer and more explicit to its intent to leverage the more concrete :stage_number field instead.
I'm sure that it's not a problem of rails admin but habtm association.
To make habtm use the right column in sql primary key must be specified for stage model and foreign key for association.
And it is the only way to make it works right.
class Stage < ActiveRecord::Base
self.primary_key = "stage_number"
has_and_belongs_to_many :bands, foreign_key: :stage_number
end
But I think the best way is to use joint model and has_many/belongs_to because for has_many/belongs_to it's possible to set any column to be used as primary key via :primary_key option.
class BandStageLink < ActiveRecord::Base
self.table_name = "bands_stages"
belongs_to :band
belongs_to :stage, foreign_key: :stage_number, primary_key: :stage_number
end
class Band < ActiveRecord::Base
has_many :band_stage_links
has_many :stages, through: :band_stage_links, foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_many :band_stage_links, primary_key: :stage_number, foreign_key: :stage_number
has_many :bands, through: :band_stage_links
end
Update: Note that in this case there is still no need to specify any primary keys for stage table. For instance my migration is:
class CreateStageBandTables < ActiveRecord::Migration
def change
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
create_table :bands do |t|
t.string :name
end
create_table :stages, id: false do |t|
t.integer :stage_number
t.string :name
end
end
end
I tested both cases for rails 4.2.5 and everything works just fine.
Edit - I did mis-understand the primary key bit, I think the desire was to tell Rails to use different attribute as PK, which should be less problematic than re-purposing the auto-increment-by-default PK ID. In that case, the Stage model should include self.primary_key = "stage_number", and the rest of the details at the bottom of this answer relating to HABTM alongside that. Of course has-many-through would still be my preferred solution here.
I think there's a bigger problem with the models and approach, than Rails Admin.
If I understand what you're trying to do, then you'd also need to turn off auto-increment for the primary key in stages table, to hold arbitrary numbers (representing stage numbers) as primary key IDs. It could end badly very quickly, so I'd advise against it.
If the data is genuinely static (10 stages ever), you could even keep it as a constant in the Band model and scrap Stage completely (unless there's more there), e.g.
class Band < ActiveRecord::Base
POSSIBLE_STAGES = [1, 2, ...]
validates :stage, inclusion: { in: POSSIBLE_STAGES, message: "%{value} is not a stage we know of!" }
end
For a table-based approach, I would suggest has-many-through, it'll save you a lot of pain in the future (even if you don't need additional attributes on the join table, things like nested forms are a little easier to work with than in HABTM). Something like this:
class Band < ActiveRecord::Base
has_many :events
has_many :stages, through :events
# band details go into this model
end
class Event < ActiveRecord::Base
belongs_to :band
belongs_to :stage
# you could later add attributes here, such as date/time of event, used_capacity, attendee rating, and
# even validations such as no more than X bands on any given stage at the same time etc.
end
class Stage < ActiveRecord::Base
has_many :events
has_many :bands, through :events
# stage number/details go into this model
end
The migration for that could look something like this:
create_table :bands do |t|
t.string :bandname
# whatever else
end
create_table :events do |t|
t.belongs_to :band
t.belongs_to :stage
# you could add attributes here as well, e.g. t.integer :used_capacity
end
create_table :stages do |t|
t.integer :number
t.integer :total_capacity
# whatever else
end
As you can see primary key IDs are not touched here at all, and I would always avoid storing business data in Rails' and databases' plumbing of any sort (which is what I consider IDs to be, they're there to ensure relation/integrity of the data in a relational database, as well as nice and consistent mapping to ActiveRecord - all business data should be beside that, in actual attributes, not plumbing used to connect models).
If you still want HABTM and re-purposing of primary ID, then I think Stage should include a foreign_key statement to "advertise" itself to the bands_stages join table as having a custom key name (in bands_stages only), while keeping the association_foreign_key on the Band end to show what you want to query in the join table to reach the other side. The stages table would still utilise id though as its primary key, you'd just want to turn off auto-increment with something like t.integer :id, :options => 'PRIMARY KEY' (might be dependent on the database flavour - and again, I would advise against this).
Your models would look like this:
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: "stage_number"
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: "stage_number"
end
The connection between bands and bands_stages would be bands.id = bands_stages.band_id, for which many bands_stages.stage_number would be found, and each would be connected to stage via bands_stages.stage_number = stages.id (where stages.id has been re-purposed to represent business data at a likely future peril).
Change the association_foreign_key value to be a string instead of symbol.
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: 'stage_number'
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: 'stage_number'
end

How to properly handle multiple model associations to different keys in Rails 4.0.8

I'm having the following problem while trying to model my application, it's basically a functionality to handle bills with multiple people, like sharing the rent or any other similar stuff, the deal is:
I have a user model and a billing model but can't find out how to build the associations.
A user has_many billings and billings belongs_to user but, also the billing has_many users, e.g. Tom registers a bill that is meant to be shared by Tom himself, Betty and Bob.
So that makes Tom the creditor and the other two become debtors in the billing.
I'm kinda lost at this point, how to consolidate these associations, dunno if any more information is needed or if it's clear enough, but will appreciate any help and update with any other information needed.
Thanks in advance,
---EDIT---
I've come to the following approach:
Class User < ActiveRecord::Base
has_many :billings, foreign_key: "creditor_id", dependent: :destroy
end
Class Billing < ActiveRecord::Base
belongs_to :creditor, class_name: "User"
has_many :debts, dependent: :destroy
end
Class Debt < ActiveRecord::Base
belongs_to :billing
has_one :user
end
I also tried to graphically model it for better understanding here: imgur
Would that all be correct?
Don't be afraid to use the options available to you in ActiveRecord. If your model naming is getting cluttered (Billing belongs to a User and has_many Users) then be more specific with the association labels and sort the links with options. The Rails Associations Guide is easy to Google and covers pretty much everything.
class User < ActiveRecord::Base
has_many :billings, :foreign_key => 'creditor_id'
has_many :debts, :through => :debtor_users, :source => :billing
end
class Billing < ActiveRecord::Base
belongs_to :creditor, :class_name => 'User'
has_many :debtors, :through => :debtor_users, :source => :user
end
class DebtorUser < ActiveRecord::Base
belongs_to :billing
belongs_to :user
end
You can extend the Billing model to have multiple creditors also simply by converting the belongs_to association into a has_many :through association following the same pattern as debtors just with a different join model (say CreditorUser).

How do I create two relations in one document

I am using mongomapper and I am saving association using the following method:
class Task
include MongoMapper::Document
key :user_id, ObjectId #also works without this line
belongs_to :user
def self.add(user)
a = self.new
a.user_id = user
a.save
end
And in the User model I have added: many :Tasks
Now, I would like to save two users (in the html form I select 2 users from the Users collection), without using the array, I want to save them separately:
class Task
include MongoMapper::Document
key :from_user_id, ObjectId # user1 links to the Users model
key :to_user_id, ObjectId # user2 links to the Users model
How od I do that?
MongoMapper has similar options as ActiveRecord when it comes to specifying keys and class names. You'd do something like:
class Task
include MongoMapper::Document
key :to_user_id, ObjectId
key :from_user_id, ObjectId
belongs_to :from_user, class_name: 'User', foreign_key: :from_user_id
belongs_to :to_user, class_name: 'User', foreign_key: :to_user_id
end

Multiple has_one association to one polymorphic model

I have two models, Address and User:
class Address < ActiveRecord::Base
belongs_to :resource, polymorphic: true
end
class User < ActiveRecord::Base
has_one :contact_address, class_name: "Address", as: :resource
has_one :billing_address, class_name: "Address", as: :resource
end
Problem is if I create billing_address for User it will be automatically set as contact_address, since the addresses table doesn't specify a different resource_type (both are User).
Can you give me some advice on how I should set up my models?
Thanks
I would say a billing address is more to do with an order than a person. For example, on one order I might want you to bill me at work, another at home.
Also, a person can have many addresses, but an address can also have many people. It is a network, not a hierarchy. The classic representation:
PARTY
id
type
name
PARTY_ADDRESS
id
party_id
address_id
type {home, work}
ADDRESS
id
suite
...
ORDER
id
date
customer_party_address_id
bill_to_party_address_id
ORDER_ITEM
id
order_id
product_id
price
quantity
ship_to_party_address_id
I choose easy way, added another foreign_key for addresses table. After that:
has_one :contact_address, class_name: "Address", as: :resource, foreign_key: :foreign_key
Single table inheritance can help you out. To get this to work you have to add a type column to the locations table through a migration (as you do with all Single Table Inheritance models).
class Address < ActiveRecord::Base
belongs_to :resource, polymorphic: true
end
class ContactAddress < Address
end
class BillingAddress < Address
end
class User < ActiveRecord::Base
has_one :contact_address, as: :resource
has_one :billing_address, as: :resource
end
For what it's worth, the belongs_to relationship name (:resource) is a bit weird. Maybe :addressable would make more sense?

Ruby on Rails: Associations when a user likes a song

I'm trying to figure out the best way to setup my database and models for the following scenario.
A user can like an infinite number of songs.
A song can be liked once by an infinite number of users.
I have these tables:
songs, users, likes etc... Following RoR conventions.
The table named likes has these foreign keys: user_id, song_id. And also a field named 'time' to save a timestamp when the song was liked.
I'm not sure of how to do this, I would like to be able to use code like this in my controllers:
User.find(1).likes.all
This should not return from the likes table, but join the songs table and output all the songs that the user likes.
What are the best practises to achieve this in Ruby on Rails following their conventions?
Unless you need to act specifically on the likes table data, the model itself is probably not necessary. The relationship is easy:
class User < ActiveRecord::Base
has_and_belongs_to_many :songs
end
class Song < ActiveRecord::Base
has_and_belongs_to_many :users
end
This will join through the currently non-existent song_users table. But since you want it to join through likes you can change each one to this:
has_and_belongs_to_many :songs, :join_table => 'likes'
If you want to be able to call User.find(1).likes and get songs, then change the user's version to this:
class User < ActiveRecord::Base
has_and_belongs_to_many :likes, :join_table => 'likes', :class_name => 'Song'
end
And you could change the songs version to something like this:
class Song < ActiveRecord::Base
has_and_belongs_to_many :liked_by, :join_table => 'likes', :class_name => 'User'
end
This will give you Song.find(1).liked_by.all and give you the users (You could keep it to users if you wanted using the first version)
More details on habtm relationships can be found here: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Edit to add
If you want to act on the join table for whatever reason (you find yourself needing methods specifically on the join), you can include the model by doing it this way:
class User < ActiveRecord::Base
has_many :songs, :through => :likes
has_many :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :users, :through => :likes
has_many :likes
end
This will let you do User.find(1).songs.all, but User.find(1).likes.all will give you the join data

Resources