Let's say I have two types of users, A and B. Users of type B have fewer privileges and less-strict validations on fields (more can be blank). Otherwise, they're basically the same as type A. This fact makes me inclined to use single table inheritance.
Here's my concern - type B users can upgrade to type A. When they upgrade, they should keep all their associated records. Is there an easy way to convert from one model type to another using STI such that all associations are preserved? Can you give a simple example?
All you need to do is make sure you have given the correct value in type column.
While upgrading User B to User A, just change the value stored in type attribute to User A's
class name.
Eg:
class Staff < ActiveRecord::Base; end
class PartTimeStaff < Staff; end
part_time_staff = PartTimeStaff.first
part_time_staff.type = "Staff"
part_time_staff.save
it will upgrade the part time staff to staff class.
All associations should be remain unchanged. Since you only have one actual sql table.
All the attributes for PartTimeStaff and Staff class are kept in the same table.
See more details from Rails API and Single Table Inheritance
thanks
Maybe you can have just one User model with a type field, and validations depend on the type.
class User
validates :name, presence: true, if: :advanced_user?
protected
def advanced_user?
self.type == 2
end
end
Related
I'm building an application where users are part of an Organisation. An organisation has many Lists, which in turn have many ListItems.
Now, I would like for admin users to be able to specify which attributes are available on list items, based on the organisation they belong to (or rather, on the organisation their list belongs to), without having to touch any code.
So far, when defining attributes that are not bound to a specific column in the database, I have used document_serializable, a nifty little gem (based on virtus) which serializes virtual attributes to a JSONB column in the db. I like this approach, because I get all of virtus' goodies (types, coercion, validations, etc.), and because data ends up sitting in a JSONB column, meaning it can be loaded quickly, indexed, and searched through with relative ease.
I would like to keep using this approach when adding these user-defined attributes on the fly. So I'd like to do something like:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
organisation.list_attributes.each do |a, t|
attribute a, t
end
end
Where Organisation#list_attributes returns the user-defined hash of attribute names and their associated types, which, for example, might look like:
{
name: String,
age: Integer
}
As you might have guessed, this does not work, because organisation.list_attributes.each actually runs in the context of ListItem, which is an instance of Class, and Class doesn't have an #organisation method. I hope that's worded in a way that makes sense1.
I've tried using after_initialize, but at that point in the object's lifecycle, #attribute is owned by ActiveRecord::AttributeMethods::Read and not DocumentSerializable::ClassMethods, so it's an entirely different method and I can't figure out wether I can still access the one I need, and wether that would even work.
Another alternative would be to find the organisation in question in some explicit way, Organisation#find-style, but I honestly don't know where I should store the information necessary to do so.
So, my question: at the moment of instantiating (initializing or loading2) a record, is there a way I can retrieve a hash stored in a database column of one of its relations? Or am I trying to build this in a completely misguided way, and if so, how else should I go about it?
1 To clarify, if I were to use the hash directly like so:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
{
name: String,
age: Integer
}.each do |a, t|
attribute a, t
end
end
it would work, my issue is solely with getting a record's relation at this earlier point in time.
2 My understanding is that Rails runs a model's code whenever a record of that type is created or loaded from the database, meaning the virtual attributes are defined anew every time this happens, which is why I'm asking how to do this in both cases.
at the moment of instantiating (initializing or loading) a record, is
there a way I can retrieve a hash stored in a database column of one
of its relations?
Yes. This is fairly trivial as long as your relations are setup correctly / simply. Lets say we have these three models:
class ListItem < ApplicationRecord
belongs_to :list
end
class List < ApplicationRecord
belongs_to :organisation
has_many :list_items
end
class Organisation < ApplicationRecord
has_many :lists
end
We can instantiate a ListItem and then retrieve data from anyone of its parents.
#list_item = ListItem.find(5) # assume that the proper inherited
foreign_keys exist for this and
its parent
#list = #list_item.list
#hash = #list.organisation.special_hash_of_org
And if we wanted to do this at every instance of a ListItem, we can use Active Record Callbacks like this:
class ListItem < ApplicationRecord
belongs_to :list
# this is called on ListItem.new and whenever we pull from our DB
after_initialize do |list_item|
puts "You have initialized a ListItem!"
list = list_item.list
hash = list.organisation.special_hash_of_org
end
end
But after_initialize feels like a strange usage for this kind of thing. Maybe a helper method would be a better option!
I have a scenario where I generate reports from certain ActiveRecord models.
I have multiple roles in the application. Depending on the roles, I want to show or hide certain columns. The thing is as the number of screens/pages increase, keeping a track of these can be become a nightmare.
Is there a way in Rails, where, I can stop returning values for certain columns depending on certain conditions. For e.g. I will the object returned from a ActiveRecord.Where will have data for some columns masked depending on User's role.
You can do that using active record select method. Select only those attributes which current user role can access and pass to view.
For this you can create array of accessible feilds for paticular role in your initializer. For this create a initializer.rb file under config/initializers/. Add code something like:
ADMIN = ['feild1', 'feild2'..., 'feild10']
MANAGER = ['feild1', 'feild2'..., 'feild5']
USER = ['feild1', 'feild2', 'feild3']
in your action write code something like :
Model.select(eval(current_user.role.upcase))
In view you need to check if attribute exist in your retured activerecord or not. Otherview you will get ActiveModel::MissingAttributeError: for this:
object.has_attribute? 'att_name'
Or you can rescue it with nil
object.att_name rescue nil
I literally just wrote an answer about this - you'll probably benefit from it.
Model
It seems that if you want to return specific ActiveRecord data, there are certain ways to limit the attributes the class builds. More specifically, you can make certain methods "private" - preventing your model from returning them.
Although I'm not 100% sure on this, I can say that there are two "levels" to your question -- the database data & the model's construction. Although I don't have anything for the ActiveRecord side of things, the model can "privatize" certain attributes, preventing them from being available in other parts of your app.
A Rails model is a class - populated with attributes. This means you should be able to control which attributes are available by the Role your user is part of:
#app/models/role.rb
class Role < ActiveRecord::Base
#columns id | name | attributes | created_at | updated_at
#"attributes" can be used to assign an array
has_many :users, inverse_of: :role
end
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :role, inverse_of: :users
role.attributes.each do |attr|
private attr.to_sym
end
end
This will override the attributes pulled from the db, allowing you to determine which ones are available.
Of course, a very rudimentary procedure.
--
ActiveRecord
The best way around this will be to use ActiveRecord to specifically select the attributes / columns you want. To do this, I'm not sure of the absolute best way, but perhaps using a default_scope would be beneficial:
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :role
def attributes
case role_id
when "1"
attrs = []
when "2"
attrs = []
when "3"
attrs = []
end
end
default_scope (select: attributes)
end
Again, pretty rudimentary. I'd be interested in seeing a more integrated way of doing this.
I have two types of users (regular user, super user). What is the proper way to extend one base user class with additional tables?
I was thinking something like this but I am not sure am I going to right direction:
class User < ActiveRecord::Base
end
class SuperUser < User
end
class RegularUser < User
end
Is this the proper way to do it in Rails? Thanks :)
It is 100% correct approach, however you need to remember, that all your models will be stored in one table in database. This approach is called STI (Single table inheritance) and requires only one additional field type in you model.
If you want to have different types of users I would go with user roles versus different user tables etc.
A very good gem for that is CanCan and the documentation is excellent:
https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
You will also have nice helpers as .can? or .cannot? and more.
Yes, and you should also use single table inheritance. What this means is you should add a column called 'type' to your user model. Rails recognizes the column 'type' and treats it special. Essentially, all entries in your type model will reference another model. In that model, you can define rules for each type. It would also be a good idea to validate your user model so that only the two types you want can be entered. This should work:
class User < ActiveRecord::Base
validates :type, :inclusion => {:in => ['SuperUser', 'RegularUser']}
end
I've only been working with Rails for a few months now so I may be missing something obvious, but if an attribute is protected from mass assignment by attr_accessible, or attr_protected, how do you set that attribute when you first create a record? (And do it in a way that is still secure.)
For example:
Say I'm working on an application that keeps track of different procedures and processes that employees in a company have been trained on.
As part of this application, I have three models: User, Course, and TrainingClass. User represents employees, Course represents different things employees can be trained on, and TrainingClass represents a specific class in which employees will be trained on a course.
The TrainingClass has a belongs_to relationship with both the Course model (because a class is basically just a specific instance of a course) and the User model (because the class has a single instructor, who is the user who creates the TrainingClass). Similarly, the Course and User models both have a has_many relationship with the TrainingClass model.
Based on that information, how would I go about creating a new record for TrainingClass?
I could say:
course.training_classes.build(user_id: user.id)
or
user.training_classes.build(course_id: course.id)
But wouldn't that be mass assignment on the user_id or course_id attributes?
What would be the best way to do this while still keeping the application secure?
Read about Mass Assignment Security, specifically roles.
You could have
class TraningClass < ActiveRecord::Base
attr_accessible .....
attr_accessible ....., :user_id, as: :create
end
Where ...... is your list of other attributes you want accessible for mass assignment. Then when you call build, you'll pass that role
course.training_classes.build({user_id: user.id}, as: :create)
Repeat similarly for your other models. The default role will be used (the first one) unless you specify the :create role when calling build, create!, etc...
I'm trying to build a student portal in Rails 3, but I'm having some problem.
The idea is to have a users table that contains all basic data for a given person. See the UML/E-R below for example attributes.
A user can be both an Assistant and a Student at the same time.
Assistant and Student should inherit from User.
The idea was to inherit directly from the User, like this.
class User < ActiveRecord::Base
# ...
def awesome?
[true, false].sample
end
# ...
end
class Student < User
has_one :student
has_many :registered_courses, through: :students
end
Student.new.awesome?
This makes the relations in the student model very strange.
has_many :registered_courses, through: :students
I want to be able to do something like this in the end.
student.full_name
student.pin_code
student.registered_courses
One solution would be to implementing the method by hand, like this
class Student < User
has_one :student
def pin_number
student.pin_number
end
end
But it looks really strange to refer to a student object inside the student model.
Is there a clearer, better way of doing this?
Here is an example UML/E-R. I've tried to keep this example clean by removing non relevant attributes. That is why there are so few attributes in the registered course entity.
STI is not a good choice for this the way that you have articulated it here, since users can be both students and assistants. When you are using STI, you generally add a type column to specify which subclass the record really belongs to. If both Student and Assistant inherit from User, then that really isn't an option, since you'd be forced to create duplicate User records for someone who is both an Assistant and a Student.
I think you'd be better off simply having Student and Assistant rows that belong_to a Student, and then delegating the elements that are contained in User back to the User object.
I feel like Inheritance is a bad move here. If you're going to have STI like this it HAS to be one or the other.
Instead throw all your logic into the User model, all your data is there anyway. Plus since Student & Assistant aren't mutually exclusive there shouldn't be any methods that will override each other.
Why not STI?
STI is mainly meant for objects that contain the same data, but does different things with them.
For example, I have a specification that contains multiple processes(ex. build and test). So I have a order that contains processes.
process_1:
order_id: 1
specification: foo
type: build
process_2:
order_id: 1
specification: foo
type: test
In this example the only thing that changes in the data is the type, but because the type changes I know what process to perform from the specification.