How to implement "business rules" in Rails? - ruby-on-rails

What is the way to implement "business rules" in Rails?
Let us say I have a car and want to sell it:
car = Cars.find(24)
car.sell
car.sell method will check a few things:
does current_user own the car?
check: car.user_id == current_user.id
is the car listed for sale in the sales catalog?
check: car.catalogs.ids.include? car.id
if all o.k. then car is marked as sold.
I was thinking of creating a class called Rules:
class Rules
def initialize(user,car)
#user = user
#car = car
end
def can_sell_car?
#car.user_id == #user.id && #car.catalogs.ids.include? #car.id
end
end
And using it like this:
def Car
def sell
if Rules.new(current_user,self).can_sell_car
..sell the car...
else
#error_message = "Cannot sell this car"
nil
end
end
end
As for getting the current_user, I was thinking of storing it in a global variable?
I think that whenever a controller action is called, it's always a "fresh" call right? If so then storing the current user as a global variable should not introduce any risks..(like some other user being able to access another user's details)
Any insights are appreciated!
UPDATE
So, the global variable route is out! Thanks to PeterWong for pointing out that global variables persist!
I've now thinking of using this way:
class Rules
def self.can_sell_car?(current_user, car)
......checks....
end
end
And then calling Rules.can_sell_car?(current_user,#car) from the controller action.
Any thoughts on this new way?

I'd use the following tables:
For buyers and sellers:
people(id:int,name:string)
class Person << ActiveRecord::Base
has_many :cars, :as => :owner
has_many :sales, :as => :seller, :class_name => 'Transfer'
has_many :purchases, :as => :buyer, :class_name => 'Transfer'
end
cars(id:int,owner_id:int, vin:string, year:int,make:string,model:string,listed_at:datetime)
listed_at is the flag to see if a Car is for sale or not
class Car << ActiveRecord::Base
belongs_to :owner, :class_name => 'Person'
has_many :transfers
def for_sale?
not listed_at.nil?
end
end
transfers(id:int,car_id:int,seller_id:int,buyer_id:int)
class Transfer << ActiveRecord::Base
belongs_to :car
belongs_to :seller, :class_name => 'Person'
belongs_to :buyer, :class_name => 'Person'
validates_with Transfer::Validator
def car_owned_by_seller?
seller_id == car.owner_id
end
end
Then you can use this custom validator to setup your rules.
class Transfer::Validator << ActiveModel::Validator
def validate(transfer)
transfer.errors[:base] = "Seller doesn't own car" unless transfer.car_owned_by_seller?
transfer.errors[:base] = "Car isn't for sale" unless transfer.car.for_sale?
end
end

First, the standard rails practice is to keep all business logic in the models, not the controllers. It looks like you're heading that direction, so that's good -- BUT: be aware, there isn't a good clean way to get to the current_user from the model.
I wouldn't make a new Rules model (although you can if you really want to do it that way), I would just involve the user model and the car. So, for instance:
class User < ActiveRecord::Base
...
def sell_car( car )
if( car.user_id == self.id && car.for_sale? )
# sell car
end
end
...
end
class Car < ActiveRecord::Base
...
def for_sale?
!catalog_id.nil?
end
...
end
Obviously I'm making assumptions about how your Catalog works, but if cars that are for_sale belong_to a catalog, then that method would work - otherwise just adjust the method as necessary to check if the car is listed in a catalog or not. Honestly it would probably be a good idea to set a boolean value on the Car model itself, this way users could simply toggle the car being for sale or not for sale whenever you want them to ( either by marking the car for sale, or by adding the car to a catalog, etc. ).
I hope this gives you some direction! Please feel free to ask questions.
EDIT: Another way to do this would be to have methods in your models like:
user.buy_car( car )
car.transfer_to( user )
There are many ways to do it putting the logic in the object its interacting with.

I would think this would a prime candidate for using a database, and then you could use Ruby to query the different tables.

You might take a look at the declarative authorization gem - https://github.com/stffn/declarative_authorization
While it's pre-configured for CRUD actions, you can easily add your own actions (buy, sell) and put their business logic in the authorization_rules.rb config file. Then, in your controllers, views, and even models!, you can easily ask permitted_to? :buy, #car

I'm doing something similar with users and what they can do with photo galleries. I'm using devise for users and authentication, and then I set up several methods in the user model that determine if the user has various permissions (users have many galleries through permissions) to act on that gallery. I think it looks like the biggest problem you are having is with determining your current user, which can be handled quite easily with Devise, and then you can add a method to the user model and check current_user.can_sell? to authorized a sale.

Related

How would I implement a multi-type user system for Rails?

I'm creating a Rails application where users can sign up by checking a box in a form where they are either a "person" or "organization". I'm struggling to find a way to implement this into Rails. Both of these user types would have the same authorization. I have no idea if I want to use a string or a boolean as a data type in my ActiveRecord database. Also, what would I need to put in my model (User.rb) and my controller in order to validate it and implement it respectively?
There are many ways to implement this; it depends on what your needs are. Ask yourself: "Do people and organizations share the same attributes?"
AR Enum
If they do, the only thing that differentiates the two is role (or whatever you want to call it), i.e., person or organization. For that scenario, Rails 4.1 provides AR enums. This is the simplest solution, it could go something like this:
class User < ActiveRecord::Base
enum role: [ :person, :organization ] # #user.role => 'person', #user.person? => true
end
Polymorphic Association
On the other hand, if people and organizations share only some attributes, you might consider using a polymorphic association (If people and organizations share no attributes—not even role—they should be two different models). The base model should contain the attributes that both people and organizations share. The person/organization models should contain attributes specific to that model.
# common attributes
class User < ActiveRecord::Base
belongs_to :profile, polymorphic: true
def self.roles
%w(person organization)
end
end
# person-specific attributes
class PersonProfile < ActiveRecord::Base
has_one :user, as: :profile, dependent: :destroy
end
# organization-specific attributes
class OrganizationProfile < ActiveRecord::Base
has_one :user, as: :profile, dependent: :destroy
end
For user signup, you can create users#new and users#create actions. In your user signup form (perhaps app/views/users/new.html.erb), you could use a select_tag to let the user specify their role. Then, use that to determine what kind of profile to attach to your user model. For example (users#create):
def create
#user = User.new(user_params)
if role = params[:role]
# return HTTP 400
head :bad_request and return unless User.roles.include?(role)
# Assign the User's profile
#user.profile = "#{role.capitalize}Profile".constantize.new
else
# enter your own logic here
end
#user.save ? redirect_to(#user) : render(:new)
end
The handling of sessions (user signin/signout), in my opinion, should be handled in a separate SessionsController.
Add a new table to the database named user_types with fields role and id. And in users table you need to add user_type_id column. Then, in UserType model
class UserType < ActiveRecord::Base
has_many :users
end
And in User model you need to add
class User < ActiveRecord::Base
belongs_to :user_type
end
You can create UserType records in seeds, but make sure it runs everytime the database is reset, add this to seeds.rb
UserType.create!(role: "person")
UserType.create!(role: "organization")
Hope this makes sense!
If you have only two types of users (Person and Organization) as indicated in your question, you could just have a Person model, and add a bool field is_organization to it. For more details, See how devise, a popular authentication gem, handles this here (this approach is option 2 on the linked page, you can also check out option 1, which is to create an entire new model).

Building in model callback returning nil for value

First, thanks for taking the time to read. I'm new to Rails and have been stuck on this one for many hours.
In my Rails 3.2 app, I have three models: User, Organization, and Membership (the last is a join model between User and Organization).
When a user creates an organization, he/she should become a member upon create. So, in my Organization model, I've included a before_create callback that builds a Membership. The problem is that while the Membership builds when the new Organization is created, the user_id on the Membership object is set to "nil.," and therefore the current user is not a member.
Hardcoding in the user_id attribute in the callback actually does correctly build the membership, i.e. (:user_id => "1"), but in general asking the Organization model to be aware of current user state seems like bad MVC practice.
What's the proper way to set the current user ID on the new Membership? It seems like my associations should handle that, but I might be wrong.
Here are my models — I'm leaving out some validation lines for readability's sake. Thanks so much in advance.
user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :organizations, :through => :memberships
end
membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organization
end
organization.rb
class Organization < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
accepts_nested_attributes_for :memberships, :allow_destroy => true
...
before_create :add_membership
protected
def add_membership
self.memberships.build
end
end
You are right in the fact that allowing your model to magically know about the current user is bad MVC practice. So you have to somehow pass the current user id during creation. you can do this in many ways ; for example in the controller :
def create
#organization = Organization.new( params[:organization] ) do |org|
org.memberships.build( user_id: current_user.id )
end
# save, etc.
end
Doing this in the controller is fine, but it would be better if your business logic would reflect the fact that a user creating an organization should automatically belong to it. You could override new and / or create on Organization (or create your own method if you fear overrides) :
def new( params = {}, options = {} )
creator = options.delete( :creator )
super( params, options ) do |org|
org.memberships.build( user_id: creator.id ) if creator
yield org if block_given?
end
end
passing the user is easy now :
def create
#organization = Organization.new(params[:organization], creator: current_user)
end
If you don't like this approach, or if you don't want to override new or create a specific factory method, you can also make something similar to nested_attributes :
attr_accessible :creator_id
def creator_id=( user_id )
memberships.build user_id: user_id
end
then in your view :
f.hidden_field :creator_id, current_user.id
optional :
with first approach, for additional clarity / ease of use, you can also create a method on User :
def new_organization( params = {}, options = {}, &block )
Organization.new( params, options.merge(creator: self), &block )
end
... ok, Organization is hardcoded here (bad !) but your workflow is now quite understandable :
def create
# we know at first glance that the user is responsible for the organization
# creation, and that there must be specific logic associated to this
#organization = current_user.new_organization( params[:organization] )
# etc
end
with a little more thinking, it should be possible to avoid hardcoding Organization into User (using an association extension for instance)
EDIT
To be able to setup a validation on membership's organization presence, you need to do this :
class Organization < ActiveRecord::Base
has_many :memberships, inverse_of: :organization
end
class Membership < ActiveRecord::Base
belongs_to :organization, inverse_of: :memberships
validates :organization, presence: true
end
Let's explain this :
inverse_of sets up your associations to be bidirectional. By default, associations are one-way, which means that when you do organization.memberships.first.organization, rails tries to load the organisation again because it does not know how to "climb back" the association. When using inverse_of, rails knows it does not have to reload the organization.
validates MUST be setup on organization and NOT on organization_id. This way the validator knows we're "climbing back" the association, it knows that organization is a "parent" record and that it's in the process of being saved - so it does not complain.

multiple habtm relationships on one model

I am trying to figure out the best way to accomplish my problem. I've got a pages table, and a user_types table. I am trying to specify multiple user types on a page. They will act as permission groups. I need to do this twice however. Once for read permissions, and once for edit permissions. Here is an example:
Home page has 3 user types that can read it - admin, super admin, public
It has 2 user types that can edit it - admin, super admin
I have one user_types table:
admin
super admin
public
etc
I have created two mapping tables (one for read, and one for edit):
pages_user_read_types
pages_user_edit_types
they both have page_id, and user_type_id
Is there a better way to accomplish this? If this is the best way, I need help figuring out the relationships for the models. I have this for one relationship
has_and_belongs_to_many :user_types, :join_table => :pages_user_read_types
How do i specify two relationships for seperate fields?
Thanks
The HABTM relationship in Rails has seemed to fall out of favor over the last couple of years with Rails developers to the has_many :through relationship. The only time you should use HABTM is when you have no need for any additional information about the relationship between two models. In your case, you are trying to emulate this by creating two HABTM relationships when you could effectively accomplish by having a join model with a editable attribute.
In code, it would look something like this:
class Page < ActiveRecord::Base
has_many :page_permissions
has_many :user_types, :through => page_permissions
def editable_user_types
page_permissions.includes(:user_types).where(:editable => true).map(&:user_type)
end
def read_only_user_types
page_permissions.includes(:user_types).where(:editable => false).map(&:user_type)
end
end
class PagePermission < ActiveRecord::Base
belongs_to :page
belongs_to :user_type
# When you create this model, you should have a boolean attribute for editable
end
class UserType < ActiveRecord::Base
has_many :page_permissions
has_many :pages, :through => :page_permissions
end
I think following this approach will allow you to consolidate to one join table which will be better in the future if you need to add additional attributes to the relationship (PagePermission) between Page and UserType.
At the very least, you probably want to add a Permission model. If it ever gets more complicated than what you've described, I would also recommend using CanCan.
class Permission < ActiveRecord::Base
#table is id, page_id, user_type_id, and permission_type (string).
belongs_to :page
belongs_to :user_type
end
In your controller, you can construct a filter chain like this:
class PagesController < ApplicationController
before_filter :load_page
before_filter :authorize_view!, only: [ :show ]
before_filter :authorize_edit!, only: [ :edit ]
def show
end
def edit
end
private
def load_page
#page = Page.find(params[:id])
end
def authorize_view!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "view").exists?
flash[:notice] = "You do not have permission to view that page."
redirect to root_path
end
end
def authorize_edit!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "edit").exists?
flash[:notice] = "You do not have permission to edit that page."
redirect to root_path
end
end
end
(This assumes you have a current_user method in your app).

Design considerations for creating associated records on Devise User object on registration

I'm using Devise, and for each User account created I want to generate a relationship where:
class User < ActiveRecord::Base
belongs_to :business
end
class Business < ActiveRecord::Base
has_many :users
has_one :apt_setting
has_many :hours, :as => :hourable
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
end
So upon registration an associated Business object is created, and with each Business object an associated ApptSettings and BusinessHour object is created.
I currently have this implemented like this:
class Admin
before_create :create_associated_records
def create_associated_records
# create the associated business object
business = Business.create(:business_name => business_name, :subdomain => subdomain, :initial_plan => initial_plan)
# retrieve the id of the new business object
self.business_id = business.id
# create the associated records
BusinessHour.default_values(business_id)
ApptSetting.default_values(business_id)
end
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
def self.default_values(business_id)
# ... create record with default values
end
end
class BusinessHour < Hour
belongs_to :hourable, :polymorphic => true
def self.default_values(business_id)
# ... create record with default values
end
end
This does work, but does it seem like the best design?
One alternative I'm considering is handling removing Admin -> create_associated_records, and instead do that work in Users::Accounts::RegistrationsController where I override the 'create' method. There I could build all the associated records, set :accepts_nested_attributes where appropriate, then call 'save' on the Business object, which should then cause all the associated records to be generated.
Thoughts on the best design, or any other ideas?
you don't need the default_values methods. In your create_associated_records you can change those calls to:
ApptSetting.create(:business_id => business_id)
Don't override the create method. before_create callbacks are a better way to go. In either case, If a business has many users, do you really want to create a new business every time a new user is created? How does a second user ever get added to a business? add something like,
def create_associated_records
return unless self.business_id.nil?
....
Also where are the business_name, subdomain, and initial_plan variables coming from in your method? Do you have them as attributes of the admin user? Seems like they should be only values of the business.
I think the biggest question here is, does a user really need a business in order to exist? Why can't the user just create their Business after they create their account?
** Edit: Being more clear / cleaner version using rails association methods:
class Admin
before_create :create_associated_records
private
def create_associated_records
return unless self.business_id.nil?
self.create_business
self.business.create_appt_setting
self.business.hours.create
end
end

How to associate a new model with existing models using has_and_belongs_to_many

I have two models with a many to many relationship using has_and_belongs_to_many. Like so:
class Competition < ActiveRecord::Base
has_and_belongs_to_many :teams
accepts_nested_attributes_for :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :competitions
accepts_nested_attributes_for :competitions
end
If we assume that I have already created several Competitions in the database, when I create a new Team, I would like to use a nested form to associate the new Team with any relevant Competitions.
It's at this point onwards that I really do need help (have been stuck on this for hours!) and I think my existing code has already gone about this the wrong way, but I'll show it just in case:
class TeamsController < ApplicationController
def new
#team = Team.new
#competitions.all
#competitions.size.times {#team.competitions.build}
end
def create
#team = Team.new params[:team]
if #team.save
# .. usual if logic on save
end
end
end
And the view... this is where I'm really stuck so I won't both posting my efforts so far. What I'd like it a list of checkboxes for each competition so that the user can just select which Competitions are appropriate, and leave unchecked those that aren't.
I'm really stuck with this one so appreciate any pointing in the right direction you can provide :)
The has_and_belongs_to_many method of joining models together is deprecated in favor of the new has_many ... :through approach. It is very difficult to manage the data stored in a has_and_belongs_to_many relationship, as there are no default methods provided by Rails, but the :through method is a first-class model and can be manipulated as such.
As it relates to your problem, you may want to solve it like this:
class Competition < ActiveRecord::Base
has_many :participating_teams
has_many :teams,
:through => :participating_teams,
:source => :team
end
class Team < ActiveRecord::Base
has_many :participating_teams
has_many :competitions,
:through => :participating_teams,
:source => :competition
end
class ParticipatingTeam < ActiveRecord::Base
belongs_to :competition
belongs_to :team
end
When it comes to creating the teams themselves, you should structure your form so that one of the parameters you receive is sent as an array. Typically this is done by specifying all the check-box fields to be the same name, such as 'competitions[]' and then set the value for each check-box to be the ID of the competition. Then the controller would look something like this:
class TeamsController < ApplicationController
before_filter :build_team, :only => [ :new, :create ]
def new
#competitions = Competitions.all
end
def create
#team.save!
# .. usual if logic on save
rescue ActiveRecord::RecordInvalid
new
render(:action => 'new')
end
protected
def build_team
# Set default empty hash if this is a new call, or a create call
# with missing params.
params[:team] ||= { }
# NOTE: HashWithIndifferentAccess requires keys to be deleted by String
# name not Symbol.
competition_ids = params[:team].delete('competitions')
#team = Team.new(params[:team])
#team.competitions = Competition.find_all_by_id(competition_ids)
end
end
Setting the status of checked or unchecked for each element in your check-box listing is done by something like:
checked = #team.competitions.include?(competition)
Where 'competition' is the one being iterated over.
You can easily add and remove items from your competitions listing, or simply re-assign the whole list and Rails will figure out the new relationships based on it. Your update method would not look that different from the new method, except that you'd be using update_attributes instead of new.

Resources