How to implement "business rules" in 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开发者_C百科? @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.
精彩评论