I wondering is there a way to represent model differently (or probably control access on fields level) depending on it's (model instance) state and controller using it.
Example:
Imagine we have an Order model with product_id, count, price and status fields.
status could be one of: :new, :confirmed, :accepted, :cancelled, :delivered and :closed.
Application can access Order from, say, two controllers CustomerOrdersController and SellerOrdersController. So, CustomerOrdersController could create and edit orders. But able to change only count field. On the other hand SellerOrdersController could edit orders. But able to change only price field. I.e. it would be great if instance of Order class that CustomerOrdersController working with have no price= method. Same for count=(product=) and SellerOrderController.
Further more set of columns permitted to edit depends on status field (probably work for some state machine).
So, the question is: how would you do this in your app?
PS
I think about some ActiveModel proxy objects for ActiveRecord instances, but do not know actually will it work or not. Consider:
class CustomerOrderProxy < ActiveModel::Base end
class SellerOrderProxy < ActiveModel::Base end
class Order < ActiveRecord::Base
def wrap_proxy(controller_user)
controller_user == CustomerOrdersController ? CustomerOrderProxy(self) : SellerOrderProxy(self)
end
end
Another approach would be to do tons of checks and params validations inside controller actions, but I do not want to. I believe in "Fat model - skinny controller" :)
PPS
I know that ruby have plenty state machine plugins, but AFAI understand they define only transitions, not method set (i.e. representation) of the object.
This sounds like simple access control. Access is granted based on the authorized user, not which controller is being used. Take a look at the cancan gem for implementing clean, declarative access control for your AR objects.
Looks like I've found appropriate solution: in Ryan Bates's screencast Dynamic attr_accessible
Update:
In Rails 3.1 update_attributes(params[:order], role) could be used. Check out rails api. Though it cannot be used to change access control according to object's state.
Related
I have a Model Bot and I would like to ensure that there is only one Bot object in my database. I also need to make sure it is persisted and not tampered with.
My original thought was to do this in a migration, one that would follow the :bots table migration. It would include a line that is something like:
Bot.all.size == 0 ? Bot.create! : nil
Maybe this would prevent the AR object from being messed with in future migrations?
BONUS: Would be awesome to be able to have instant and global access to this class object. I was thinking using a singleton module in my Bot class that way I can always reference Bot.instance and have access to that specific object.
USE CASE:
I have 4 types of users in my DB and this bot will be the facilitator to delivery role-specific messages to them through our in-app messaging feature.
The Class Bot will have a has_many association with BotMessage/bot_messages. On the bot_messages table will be an enum field for user_role.
Messages will be created by company admins and stored in these tables because we want them to be viewable at any time by looking at the "conversation" thread between the User and the Bot.
When it comes to only having 1 bot, it's just that. I have no need for an additional Bot object. Additionally, since there is only one object it would be nice to be able to have a way of explicitly targeting that object without having to run a query to find it.
For example, unlike User where there could be 1000 records and in order to find the specific one you would do something like #user = User.find_by_email('foo#bar.com'), doing something like that for the bot would be unnecessary since there is only one record to find. That is what lead me to believe having a singleton object may be worthwhile here, so whenever I need to pull up a message for a specific role, I could run Bot.instance.bot_messages.where(user_role: 1) or something similar
Based on your Use Case, I see no reason for Bot to be a model.
Let's say you have a role called cool_user and you want to get all the bot_messages for that role, you might do something like:
class Bot
class << self
def bot_messages(user_role)
BotMessage.send(user_role)
end
end
end
As a very thoughtful but potentially anonymous super code monkey notes in the comments, you could also do:
class Bot
def self.bot_messages(user_role)
BotMessage.send(user_role)
end
end
Which some folks might find more readable. IMO, it is a bit of an issue of personal preference.
In either case, you should be able to do
Bot.bot_messages(:cool_user)
Since, as stated in the docs,
Scopes based on the allowed values of the enum field will be provided as well.
So, I believe BotMessage, with the properly set enum, should respond to cool_user and return all the bot_messages for that role.
You may need to check the docs to get the syntax exactly right.
I believe this should also satisfy your BONUS requirement.
A proven solution would be to use an STI on User (with a user_type column)
class User < ApplicationRecord
...
end
class Bot < User
has_many :bot_messages, foreign_key: :user_id
end
Is it what you're looking for ?
My Rails app has many models that form a hierarchy. For example: Retailer > Department > Product Category > Product > Review.
A business requirement is that high-authority users can "share" any individual element in the hierarchy with a new or existing "normal" user. Without having an object shared with them, normal users have no rights to see (or do anything else) with any object in any level of the hierarchy.
The sharing process includes a choice of whether the share grants permission to read-only, read-update, or full CRUD on the target object.
Sharing any object grants R/O, R/W or CRUD permission to that object and all lower level objects in the hierarchy, and R/O permission to all of the direct ancestors of the object. The object collection grows organically, so the permission system works by just logging the user_id, the object_id of the share, and the nature of the share (R/O, CRUD, etc). As the population of objects in this hierarchy grows all the time, it is impractical to create an explicit permission record in the DB for every user/object combination.
Instead, at the start of the user request cycle, ApplicationController gathers all the permission records (user X has CRUD permission to Department #5) and holds them in a hash in memory. A Permissions model knows how to evaluate the hash when any object is passed to it - Permission.allow?(:show, Department#5) would return true or false depending on the content of the user's permission hash.
Let's take, for example, the Department model:
# app/models/department.rb
class Department < ActiveRecord::Base
after_initialize :check_permission
private
def check_permission
# some code that returns true or false
end
end
When the check_permission method returns true, I want Department.first to bring back the first record in the database as normal, BUT, if check_permission returns false, I want to return nil.
Right now, I have a solution whereby default scopes trigger a permissions check, but this is causing 2X the number of queries, and for classes with a lot of objects, memory problems and time/performance issues are sure to be on the horizon.
My goal is to use after_initialize callbacks to pre-permission the objects.
It would appear however that after_initialize is unable to block the original object from being returned. It does allow me to reset the values of the attributes of the object, but not to dispense with it.
Anybody know how to achieve this?
EDIT:
Many thanks for all of the answers and comments offered so far; hopefully this extended version of the question clarifies things.
Basically you need to check for access rights (or permissions) before returning a database query result. And you are trying to integrate this logic into your models.
It is possible, but not with the design you described in your question. It is not clean to implement this directly in ActiveRecord adapter methods (such as first, all, last etc...). You need to rethink your design.
(skip to point 'D' if this is too much reading)
You have several choices, which all depend on the way your permissions are defined. Let's look at few cases:
A. A user have a list of departments he owns and only him can access them
You can simply implement this as a has_many/belongs_to association with Active Record Associations
B. Users and Departments are independent (in other words: no ownership such as described in the previous case) and permission can be set individually for each users and each departments.
Simply again, you can implement a has_and_belongs_to_many association with Active Record Associations. You will need to create web logic so the administrator of your application can add/edit/remove access rights.
C. More complex case: the existing authorization libraries
Most people will turn to authorization solutions such as cancan, pundit or other
D. When those authorization libraries are oversized for your needs (actually, my case in most of my projects), I found that implementing authorization through rails scoping answers all my needs.
Let's see it through a simple example. I want administrators to be able to access the whole database records ; and regular users to access only departments with status = open and only during operation hours (say 8am-6pm). I write a scope that implement my permission logic
# Class definition
class Department
scope :accessible_by -> (user) do
# admin user have all access, always
if user.is_admin?
all
# Regular user can access only 'open' departments, and only
# if their request is done between 8am and 6pm
elsif Time.now.hour >= 8 and Time.now.hour <= 18
where status: 'open'
# Fallback to return ActiveRecord empty result set
else
none
end
end
end
# Fetching without association
Department.accessible_by(current_user)
# Fetching through association
Building.find(5).departments.accessible_by(current_user)
Defining a scope obliges us to use it everywhere in our code. You can think of the risk to "forget" going through the scope and accessing directly the model (i.e writing Department.all instead of Department.accessible_by(current_user)). So that's why you must solidly test your permissions in your specs (at the controller or features level).
Note In this example we do not return nil when the permission fails (as you mentioned in your question), but an empty result set instead. It is generally better so you keep the ActiveRecord method chaining capability. But you could also raise an exception and rescue it from your controller then redirect to a 'not authorized' page for example.
That is not what the after_initialize callback is used for. Instead, you could just define a method that does the same thing. For example, put this in your Department model and it should achieve the results you are looking for:
def self.get_first
check_permission ? first : nil
end
UPDATE
I'm not exactly sure how safe something like this would be, but you could just override the all method as the other query methods are based off of it.
class Department < ActiveRecord::Base
def self.all
check_permission ? super : super.none
end
private
def self.check_permission
# some code that returns true or false
end
end
You are probably better off using some authorization framework though.
UPDATE 2
Thinking about this a little more, I strongly recommend using a different approach. You really shouldn't be overriding methods like all as there will surely be unintended side effects.
A practical alternative would be to create a has_and_belongs_to_many relationship between Department and User. Here is how you would set it up:
user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :departments
...
end
department.rb
class Department < ActiveRecord::Base
has_and_belongs_to_many :users
...
end
Then run these commands in your terminal:
rails g migration CreateJoinTableDepartmentsUsers departments users
rake db:migrate
Now you can add users to a department with #department.users << #user, or departments to a user with #user.departments << #department. This should achieve the functionality that you are looking for.
#user.departments will return only departments for that user, #user.departments.first will return the first department for that user or nil if it doesn't have any, and #user.departments.find(1) will return the corresponding department only if it belongs to the user or throw an exception otherwise.
You can use before_create callback to stop record creation if check permission is false. Just return false in check_permission filter and record will be not created.
class Department < ActiveRecord::Base
before_create :check_permission
private
def check_permission
# return false if permission is not allowed
end
end
Oftentimes, I come across a situation that kind of irks me because I don't feel like my solution for it is up to par.
Say that I have a User model. There can be two TYPES of users - admins and the regular folk. In this particular case, I've simply split User and Admin into separate models - fair enough.
In some cases when there are numerous types of the same model (Foo can be green, red, purple, yellow, etc)
I don't see much sense in dealing with 10 separate models like YellowFoo, GreenFoo, etc - so I add a "type" attribute to the model and then the code for, say, finding the correct object is simplified to User.where(:type => "some_type").
Is this an acceptable way of doing things in Ruby on Rails? Should the type be set to a be Symbol instead of a string so that User.where(:type => :some_type) looks prettier or is this a hack no matter how you look at it?
Same goes for views that I create. In the above example, there is an admin controller with a whole separate dashboard from the users, even though the code for the dashboard is more or less the same, with a few exceptions (but in the future, potentially far more different).
Is this an acceptable way of doing things or the newb way?
Thanks in advance!
type attribute is good enough to use it inside the model to separate different types of objects. But type attribute name itself used by Rails AR class method, so I'd suggest to use model name specified field type name like 'user_type' for User model to avoid conflicts.
Having defined model's types you can add very handy model's scopes and methods.
For example:
class Order
# I prefer to define type constants as camelized strings, but it would be probably
# better to use strings in downcase for localization purposes
ORDER_TYPES = (OrderTypePersonal, OrderTypeOrganization = 'Personal', 'Organization')
scope :personal, where(order_type: OrderTypePersonal)
scope :organization, where(order_type: OrderTypeOrganization)
def personal?; order_type == OrderTypePersonal end
def organization; order_type == OrderTypeOrganization end
end
# find all personal orders
personal_orders = Order.personal.all
# build new organization order
organization_order = Order.organization.new
# check order type
Order.find(14).personal?
# user order types as select
f.select :order_type, Order::ORDER_TYPES
I would suggest for different types/roles of users in our application, we can define a Role model.
We can define as the associations between User and Role as per our requirement. More basically,
In Role Model we will define
has_many :users
In User model we will define
belongs_to :role
A role will have multiple users. We can check each users role in User model
def has_role?(roleSymbol)
(role.role_name.underscore.humanize.downcase.to_sym == roleSymbol)? true :false
end
Here we can check the user has a role of "Admin" or "Normal" user.
For view parts we can check the user roles and can include separate partial for different types of users.
I know the dogma says to not access current_user in a model but I don't fully agree with it. For example, I want to write a set of logging functions when an action happens via a rails callback. Or simply writing who wrote a change when an object can have multiple people write to it (not like a message which has a single owner). In many ways, I see current_user more as config for an application - in other words make this app respond to this user. I would rather have my logging via the model DSL rather than in the action where it seems REALLY out of place. What am I missing?
This idea seems rather inelegant Access current_user in model
as does this: http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
thx
edit #1
So my question isn't if there are gems that can do auditing / logging. I currently use paper_trail (although moving away from it because I can do same functionality in approx 10 lines of ruby code); it is more about whether current_user should never be accessed in the model - I essentially want to REDUCE my controller code and push down logic to models where it should be. Part of this might be due to the history of ActiveRecord which is essentially a wrapper around database tables for which RoR has added a lot of functionality over the years.
You've given several examples that you'd like to accomplish, I'll go through the solution to each one separately:
I want to write a set of logging functions when an action happens via
a rails callback
Depending on how you want to log (DB vs writing to the logger). If you want to log to the DB, you should have a separate logging model which is given the appropriate information from the controller, or simply with a belongs_to :user type setup. If you want to write to the logger, you should create a method in your application controller which you can call from your create and update methods (or whatever other actions you wanted to have a callback on.)
Or simply writing who wrote a change when an object can have multiple people write to it
class Foo < ActiveRecord::Base
belongs_to :user, as: :edited_by
end
class FooController < ApplicationController
def update
#foo = Foo.find(params[:id])
#foo.attributes = params[:foo]
#foo.edited_by = current_user
end
end
I think you're misunderstanding what the model in Rails does. Its scope is the database. The reason it can't access current_user, is because the current user is not stored in the database, it is a session variable. This has absolutely nothing to do with the model, as this is something that can not exist without a browser.
ActiveRecord::Base is not a class that is designed to work with the browser, it is something that works with the database and only the database. You are using the browser as an interface to that model, but that layer is what needs to access browser specific things such as session variables, as your model is extending a class that is literally incapable of doing so.
This is not a dogma or style choice. This is a fact of the limitations of the class your model is extending from. That means your options basically boil down to extending from something else, handling it in your controller layer, or passing it to the model from your controller layer. ActiveRecord will not do what you want in this case.
The two links you show (each showing imho the same approach) is very similar to a approach I still use. I store the current_user somewhere (indeed thread-context is the safest), and in an observer I can then create a kind of audit-log of all changes to the watched models, and still log the user.
This is imho a really clean approach.
An alternative method, which is more explicit, less clean but more MVC, is that you let the controller create the audit-log, effectively logging the actions of the users, and less the effects on different models. This might also be useful, and in one website we did both. In a controller you know the current-user, and you know the action, but it is more verbose.
I believe your concerns are that somehow this proposed solution is not good enough, or not MVC enough, or ... what?
Another related question: How to create a full Audit log in Rails for every table?
Also check out the audited gem, which solves this problem as well very cleanly.
Hope this helps.
To preserve data integrity, I need to prevent some models from being modified after certain events. For example, a product shouldn't be allowed to be written off after it has been sold.
I've always implemented this in the controller, like so (pseudo-ish code):
def ProductsController < ApplicationController
before_filter require_product_not_sold, :only => [ :write_off ]
private
def require_product_not_sold
if #product.sold?
redirect_to #product, :error => "You can't write off a product that has been sold"
end
end
end
It just struck me that I could also do this in the model. Something like this:
def Product < ActiveRecord::Base
before_update :require_product_not_sold
private
def require_product_not_sold
if self.written_off_changed?
# Add an error, fail validation etc. Prevent the model from saving
end
end
end
Also consider that there may be several different events that require that a product has not been sold to take place.
I like the controller approach - you can set meaningful flash messages rather than adding validation errors. But it feels like this code should be in the model (eg if I wanted to use the model outside of my Rails app).
Am I doing it wrong?
What are the advantages of handling this in my model?
What are the disadvantages of handling this in my model?
If I handle it in the model, should I really be using validates rather than a callback? What's the cleanest way to handle it?
Thanks for your ideas :)
It seems like you already have this one covered, based on your question. Ideally a model should know how to guard its state, as data objects are typically designed with portability in mind (even when they'll never be used that way).
But in this case you want to prevent an action before the user even has access to the model. Using a model validation in this case means you're too late and the user has already gone farther than he should by having access to and attempting to write off a product which should never have been accessible based on its sold status.
So I guess the ideal answer is "both." The model "should" know how to protect itself, as a backup and in case it's ever used externally.
However in the real world we have different priorities and constraints, so I'd suggest implementing the change you listed if feasible, or saving it for the next project if not.
As far as using a model callback versus a validation, I think that's a trickier question but I'll go with a validation because you'd likely want to present a message to the user and validation is built for exactly that use (I'd consider this more of a friendly and expected user error than a hostile or security-related one which you might handle differently).
Is that along the lines of what you've been considering?