I got the following use-case:
I got three types of Users: Advertisers, Publishers and Administrators. Each user has some common properties (like name or surname) but also several unique associations. The advertiser has an association with Ad(verttisement)s and Campaigns. Each of with is another model of its own.
My question is how would I go about and model that using ActiveRecords? What would the migration code look like?
Here are the model classes:
User:
class User < ActiveRecord :: Base
require 'pbkdf2'
require 'date'
has_many :messages
attribute :name, :surname, :email, :password_hash, :password_salt
attr_accessor :password, :password_confirmation, :type
attribute :user_since, :default => lambda{ Date.today.to_s }
[...]
end
Publisher:
class Publisher < User
has_many :websites
end
Advertiser:
class Advertiser < User
has_many :campaigns
has_many :ads
end
I got the following migration file to create the User:
class AddUser < ActiveRecord::Migration
def up
create_table :users do |t|
t.string :name
t.string :surname
t.string :email
t.string :password_hash
t.string :password_salt
t.date :user_since
t.string :type
end
create_table :messages do |t|
t.belongs_to :user
t.string :account_number
t.timestamps
end
end
def down
drop_table :user
end
end
How do I modify this file in order to incorporate the aforementioned associations?
Edit: Corrected the associations to use plural form.
Polymorphic relationships is one way to solve this, while another way would be to use Single Table Inheritance (STI). Each approach has its benefits and drawbacks, and your decision would probably depend in how different the subclasses of User would tend to be. The more drastically they would differ, the more the decision would tend toward polymorphic relationships.
Using STI approach:
# a single :users table
# one table for each of the other (non-user) models
class User < ActiveRecord::Base
has_many :messages
end
class Publisher < User
has_many :websites
end
class Advertiser < User
# if :campaign supports multiple user-types (polymorphic)
has_many :campaigns, :as => :user
# otherwise
has_many :campaigns
has_many :ads
end
class Message < ActiveRecord::Base
belongs_to :user
end
class Campaign < ActiveRecord::Base
# if multiple user-types will have campaigns
belongs_to :user # referential column should be :user_id
# otherwise
belongs_to :advertiser # referential column should be :advertiser_id
end
Using Polymorphic approach:
# there should be no :users table, as User will be an abstract model class
# instead make a table for each of all the other models
class User < ActiveRecord::Base
self.abstract_class = true
has_many :messages, :as => :messageable
end
class Publisher < User
has_many :websites
end
class Advertiser < User
has_many :campaigns
has_many :ads
end
class Message < ActiveRecord::Base
belongs_to :messageable, polymorphic: true # referential columns should be :messageable_id and :messageable_type
end
class Campaign < ActiveRecord::Base
# if multiple user-types will have campaigns
belongs_to :user, polymorphic: true # referential columns should be :user_id and :user_type
# otherwise
belongs_to :advertiser # referential column should be :advertiser_id
end
Related
I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)
First I would like to know if there is any possibilities to associate one of my model with the ActiveAdmin::Comment and the AdminUser models
this is my model
class AdminAction < ActiveRecord::Base
has_one :comment, :class_name => "ActiveAdmin::Comment", :foreign_key => "admin_action_id"
belongs_to :admin_user
end
thoses associations don't raise any errors, just returning `nil``
I have added a field in thoses two models :
add_column :admin_users, :admin_action_id, :integer
add_column :active_admin_comments, :admin_action_id, :integer
The goal here is to fetch the AdminUser and the Comment associate to my new model AdminAction
and when I do
a = AdminAction
a.admin_user
# and
a.comment
it works
any ideas ?
You need to have a admin_user_id in the admin_actions table to make this belongs_to association work.
class AdminAction < ActiveRecord::Base
belongs_to :admin_user
end
Also, the foreign_key param is unneeded because it will be inferred from the AdminAction class name.
class AdminAction < ActiveRecord::Base
has_one :comment, :class_name => "ActiveAdmin::Comment", :foreign_key => "admin_action_id"
end
Other than that, what you have should work as expected. If it is not, please provide more detail as to what you are seeing, or not seeing as the case may be.
I have this working, albiet with a User model rather than AdminUser. Here is my code:
Migrations
class CreateAdminAction < ActiveRecord::Migration
def change
create_table :admin_actions do |t|
t.references :user, index: true
t.timestamps
end
end
end
class AddFieldsForAdminAction < ActiveRecord::Migration
def change
add_column :active_admin_comments, :admin_action_id, :integer
end
end
AdminAction class
class AdminAction < ActiveRecord::Base
has_one :comment, class_name: 'ActiveAdmin::Comment'
belongs_to :user
end
Another thought: if you are looking to get the ActiveAdmin::Comment records for a single AdminUser, I think you can fetch them directly like this:
admin_comments = ActiveAdmin::Comment.find_for_resource_in_namespace(AdminUser.find(some_id), :admin)
I have a simple relationship
class School < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :schools
end
A user can be part of many schools but at the same time a user might be the admin of a number of schools. I set up a many-to-many relationship to represent this however I'm not sure how I would distinguish between admins and simple users.
I initially thought of setting a table which has a school_id and a user_id and every entry will represent the school id and the user id of any admins that the school has however I'm not sure how I would represent this in rails or if it's the best way to solve this problem? And if it is, how do I access the table without a model associated to it?
What I mean by what I said above:
school_id user_id
1 3
1 4
Which means that the school with id 1 has 2 admins (3 and 4)
What you are looking for is a more complex many_to_many relationship between school and user called has_many :through. This relationship allows you to have many to many relationship with access to the table that represents the relationship. If you use that relationship, your models should look something like this:
class User < ActiveRecord::Base
has_many :school_roles
has_many :schools, through: :school_roles
end
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
end
class School < ActiveRecord::Base
has_many :school_roles
has_many :users, through: :school_roles
end
And the migrations of those tables would look something like this:
class CreateSchoolRoles < ActiveRecord::Migration
def change
create_table :schools do |t|
t.string :name
t.timestamps null: false
end
create_table :users do |t|
t.string :name
t.timestamps null: false
end
create_table :school_roles do |t|
t.belongs_to :school, index: true
t.belongs_to :user, index: true
t.string :role
t.timestamps null: false
end
end
end
I would suggest to make the "role" field in the "school_roles" migration an integer and then use an enum in the model like so:
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
enum role: [ :admin, :user ]
end
which allows you to add more roles in the future, but it's your call
combining polymorphic association with has_many :through in my opinion is best option.
Let's say you create supporting model SchoolRole, which
belongs_to :user
belongs_to :school
belongs_to :rolable, polymorphic:true
This way:
class School ...
has_many :administrators, :as => :schoolroles
has_many :users, :through => :administators
#school.administrators= [..., ...]
It is quite agile.
#user=#school.administrators.build()
class User
has_many :roles, :as => :rolable
def admin?
admin=false
self.roles.each do |r|
if r.role_type == "administator"
admin=true
break
end
end
admin
end
....
I am using devise for authentication and finding a way to get out of this.
Can I explore same design user having multiple roles ?. So that he can login as Teacher or Parent both?. Basically he can switch accounts like multiple roles.
class User < ActiveRecord
belongs_to :loginable, polymorphic: true
end
class Parent < ActiveRecord
has_one :user, as: :loginable
end
class Teacher < ActiveRecord
has_one :user, as: :loginable
end
for eg: loginable_type: "Parent", loginable_id: 123
I want to find a way to change above fields, if user is logging in as 'Teacher' and its ID.
You can add a polymorphic has_many relationship:
class CreateUserRoles < ActiveRecord::Migration
def change
create_table :user_roles do |t|
t.integer :role_id
t.integer :user_id
t.string :role_type # stores the class of role
t.timestamps
end
add_index :user_roles, [:role_id, :role_type]
end
end
class AddActiveRoleToUser < ActiveRecord::Migration
def change
change_table :user_roles do |t|
t.integer :active_role_id
t.timestamps
end
end
end
class User < ActiveRecord
has_many :roles, polymorphic: true
has_one :active_role, polymorphic: true
def has_role? role_name
self.roles.where(role_type: role_name).any?
end
end
I'm trying to use STI because I want to use single sign-in page for a device. I want to assign either teacher_id or student_id to a user, but it turned out that all the user has both. How can I fix this problem? Below are the models and the migration.
class User < ActiveRecord::Base
...
DEFAULT_ROLE = 'Student'
after_create :set_role
attr_accessible ..., :role
has_one :role
...
private
def set_role
self.role ||= Role.find_by_name(DEFAULT_ROLE)
end
...
end
class Student < User
has_many :bookings
end
Class Teacher < User
has_many :bookings
end
class Role < ActiveRecord::Base
validates :name, :presence => true
belongs_to :user
end
Class Booking < ActiveRecord::Base
attr_accessible :student_id, :teacher_id
belongs_to :teacher, :class_name => 'Teacher'
belongs_to :student, :class_name => 'Student'
...
class CreateBookings < ActiveRecord::Migration
def change
create_table :bookings do |t|
t.integer :student_id
t.integer :teacher_id
t.date :booking_date
t.time :booking_time
t.timestamps
end
end
end
It looks like you need to separate the "role" part of User into a separate object and then allow users to have multiple roles. Sometimes these are called "profiles" as they really refer to a way of presenting the user.
You can then use the user model as a proxy for accessing these things where you'd test for the presence of the profile:
if (user.teacher)
# ...
else
flash[:notice] = "You must be a teacher to perform this operation."
end