The way my site operates, some records are created by the user but are only partially filled out. An administrator has to complete some of the record fields. I was thinking about putting all the validations related to the administrative fields in a subclass.
For example, in /app/models/document.rb:
class Document < ActiveRecord::Base
# minimal validations needed, etc
end
In /app/models/admin/document.rb (I'm not even sure if the syntax below is valid Ruby)
class Admin::Document < Document
# Extra validations for the fields the admin
end
Would my approach be a bad idea? I also plan on having role based authentications using something like CanCan.
Perhaps an alternative route would be to use CanCan to allow admins to complete a users document but instead of subclassing the record or splitting it over two. When the form is submitted add an administrated ( or approved ) field then if its one admin or several you can search by the unadministrated( or unapproved )
Surely at the end of the day the admin does the final submission so they can then set the administrated ( or approved ) to true. Thereby finalizing the document?
I think it might be better to keep all of the validations in one class, and have a boolean database column that stores if the record has been completed by the administrator.
class Document < ActiveRecord::Base
attr_protected :completed_by_administrator # do not allow this to be set via mass assignment
validate ..., :if => :completed_by_administrator # only do these validates if completed_by_administrator is true
end
When the record is first created, completed_by_administrator is false and the admin-only validations do not run. When the record is completed by an administrator, set completed_by_administrator = true in your controller and the admin-only validations automatically run before the record is saved.
Related
I'm writing a web app using Rails, part of which includes giving users the ability to leave reviews for things. I wanted to put a validation in the review model to ensure that one user can't leave multiple reviews of the same item, so I wrote this:
class NoDuplicateReviewValidator < ActiveModel::Validator
def validate(record)
dup_reviews = Review.where({user_id: record.user,
work_id: record.work})
unless dup_reviews.length < 1
record.errors[:duplicate] << "No duplicate reviews!"
end
end
end
This validator has the desired behavior, i.e. it guarantees that a user can't review a work twice. However, it has the undesired side-effect that a user can't update an already existing review that he/she left. I'm using a really simple
def update
#review.update(review_params)
respond_with(#work)
end
in the reviews controller. How can I change either the validator or the update method so that duplicate reviews are prevented but updates are allowed?
I'm very new to Rails and web development, so I'm sure I've done something goofy here. I didn't use one of the built-in unique validators because what is unique is the user/work pair; there can more than one review by the same user of different works, and there can be more than one review of the same work by different users.
You can use validates_uniqueness_of on multiple attributes, like this:
validates_uniqueness_of :user_id, :scope => :work_id
Then a user would not be allowed to review a already reviewed work.
#Sharvy Ahmed's answer is definitely the best, as long as the case is simple enough – the OP's case seems like one of them.
However, if the conditions are more complex, you may need/want to write your custom validation. For that purpose, here's an example (checked with Rails 6.0).
class NoDuplicateReviewValidator < ActiveModel::Validator
def validate(record)
dup_reviews = Review.where(user_id: record.user,
work_id: record.work)
dup_reviews = dup_reviews.where.not(id: record.id) unless record.new_record?
if dup_reviews.count > 0
record.errors[:duplicate] << "No duplicate reviews!"
end
end
end
The idea is,
In create, all the relevant DB records retrieved with where can and should be used to judge the uniqueness. In the example new_record? is used to check it out, but it is actually redundant (because nil id matches no records).
In update, the DB row of the record to update must be excluded from the unique comparison. Otherwise, the update would always fail in the validation.
The count method is slightly more efficient in terms of DB transaction.
So for some reason, my client will not drop inactive users from their database. Is there a way to globally exclude all inactive users for all ActiveRecord calls to the users table?
EX: User.where("status != 'Inactive'")
I want that to be global so I don't have to include that in EVERY user statement.
Yes, you can set a default scope:
class User < ActiveRecord::Base
default_scope where("status != 'Inactive'")
end
User.all # select * from users where status != 'Inactive'
... but you shouldn't.
It will only lead to trouble down the road when you inevitably forget that there is a default scope, and are confused by why you can't find your records.
It will also play havoc with associations, as any records belonging to a user not within your default scope will suddenly appear to belong to no user.
If you had a simple setup with posts and users, and users had a default scope, you'd wind up with something like this:
# we find a post called 1
p = Post.first # <#post id=1>
# It belongs to user 2
p.user_id # 2
# What's this? Error! Undefined method 'firstname' for `nil`!
p.user.first_name
# Can't find user 2, that's impossible! My validations prevent this,
# and my associations destroy dependent records. Can't be!
User.find(2) # nil
# Oh, there he is.
User.unscoped.find(2) <#user id=2 status="inactive">
In practice, this will come up all the time. It's very common to find a record by it's ID, and then try to find the associated record that owns it to verify permissions, etc. Your logic will likely be written to assume the associated record exists, because validation should prevent it from not existing. Suddenly you'll find yourself encountering many "undefined method blank on nil class" errors.
It's much better to be explicit with your scope. Define one called active, and use User.active to explicitly select your active users:
class User < ActiveRecord::Base
scope :active, -> where("status != 'Inactive'")
end
User.active.all # select * from users where status != 'Inactive'
I would only ever recommend using a default_scope to apply an order(:id) to your records, which helps .first and .last act more sanely. I would never recommend using it to exclude records by default, that has bitten me too many times.
Sure, in your model define a default scope
see here for more info
eg
class Article < ActiveRecord::Base
default_scope where(:published => true)
end
Article.all # => SELECT * FROM articles WHERE published = true
As an alternative to #meagar's suggestion, you could create a new table with the same structure as the Users table, called InactiveUsers, and move people into here, deleting them from Users when you do so. That way you still have them on your database, and can restore them back into Users if need be.
In general, I have a website which needs to have complex registration process. And in that registration process I need to include 4 tables from database.
Now... I cannot validate one by one model and to enter 4 of them in database. Is there a way to make common points of all those tables in one model?
Let's say:
User model has columns: username, name, etc.
Package model has: type, account_number
etc
And in registration process I need to include username, name, account_number and to validate them. How to do that?
Without seeing your model structure, this is just speculation, but here goes:
--
Virtual Attributes
In your User model, you can use attr_accessor to create a set of virtual attributes - which basically mean you can create a series of setter / getter methods in your User model.
Although I don't think this will help you directly, it should give you an idea as to how you can create single-model validation:
#app/models/user.rb
Class User < ActiveRecord::Base
attr_accessor :new, :values, :to, :validate
validates, :new, :values, :to, :validate, presence: true
end
This will allow you to create the attributes in the User model - and although they won't be saved, you can then use them to validate against
attr_accessor
To give you a quick overview of this method, you first need to remember that Rails is just a collection of modules and classes.
This means that every model you load is just a class which has been populated with a series of getter / setter methods. These methods are defined by ActiveRecord from your data table columns, and are why you can call #user.attribute
The magic is that if you use attr_accessor, you'll basically create your own attributes in your User model - which won't be saved in the database, but will be treated like the other attributes your objects have, allowing you to validate them
Because your registration process seems to be complex, I would go even futher as virtual attributes and use Form Objects
7 Patterns to Refactor Fat ActiveRecord Models
LA Ruby Conference 2013 Refactoring Fat Models
ActiveModel Form Objects
I understand that you multistep registration. You shouldn't create 4 models only because your view pages needs it. You should:
remove validation from User model and add validation on each form
create 4 different forms (for example extends by ActiveModel or user gem reform)
add validation to each form
after form.valid? save part of user info to #user object
Thats all.
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 want to make a record management system. The system will have 4 different user roles: Admin, Viewer, Editor and Reviewer.
While the first two are easy to implement using gems such as cancan and declarative authorization, the other two are not so simple.
Basically each new record is created by an Admin (only an Admin can create new records), and should have its own separate Editor and Reviewer roles. That is, a user can be assigned many different roles on different records but not others, so a user might be assigned Editor roles for Record A and C but not B etc.
Editor: can make changes to the record, and will have access to specific methods in the controller such as edit etc.
Reviewer: will be able to review (view the changes) made to the record and either approve it or submit comments and reject.
Viewer: Can only view the most recent approved version of each record.
Are there any ways of handling such record-specific user roles?
This can be accomplished without too much effort with the cancan gem and a block condition. A block condition checks for authorization against an instance. Assuming your Record class had an editors method that returns an array of authorized editors the cancan ability for updating a Record might look something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
...
can :update, Record do |record|
record.editors.include?(user)
end
...
end
end
See "Block Conditions" on the CanCan wiki:
https://github.com/ryanb/cancan/wiki/Defining-Abilities
Update
Storing which users have which access to which records could be done many ways depending on your specific needs. One way might be to create a model like this to store role assignments:
class UserRecordRoles < ActiveRecord::Base
# Has three fields: role, user_id, record_id
attr_accessible :role, :user_id, :record_id
belongs_to :user_id
belongs_to :record_id
end
Now create a has_many association in the User and Record models so that all role assignments can be easily queried. An editors method might look like this:
class Record < ActiveRecord::Base
...
has_many :user_record_roles
def editors
# This is rather messy and requires lot's of DB calls...
user_record_roles.where(:role => 'editor').collect {|a| a.user}
# This would be a single DB call but I'm not sure this would work. Maybe someone else can chime in? Would look cleaner with a scope probably.
User.joins(:user_record_roles).where('user_record_roles.role = ?' => 'editor')
end
...
end
Of course there are many many ways to do this and it varies wildly depending on your needs. The idea is that CanCan can talk to your model when determining authorization which means any logic you can dream up can be represented. Hope this helps!