Status model design pattern - ruby-on-rails

I'm running into problems implementing statuses for a model. This is probably due to wrong design.
There is a model which has a status. There can be multiple instances of the model and only a few predefined statuses (like: created, renewed, retrieved etc.). For each individual status there is some calculation logic for the model. E.g. model.cost() is differently calculated for each status.
I'd like to have ActiveRecord automatically set the correct model_status_id when saving a model. I think in the ideal situation I could do something like this:
model.status = StatusModel.retrieved
and
case status
when renewed
# ...
when retrieved
# ..
end
Thinking i need to save the status in the model row in the database this is what i've got now:
ModelStatus < ActiveRecord::Base
has_many :models
Model < ActiveRecord::Base
belongs_to :model_status
However this is giving me a lot of issues in the code. Anyone having some good ideas or patterns for this?

What you describe seems like a perfect case for a state machine.
There are many Ruby state machine implementations. You can see a fairly representative list at ruby-toolbox
When defining a state machine you can define several States and transitions. Each transitions taking your model from one state to another executing some code along the way. The DSL for it is usually quite nice.
Your example would look like
model.retrieve!
This will change the mode status from whatever it was to retrieved or throw an exception if current status doesn't transition to retrieved.

Why not keep the status part of the actual model? If they are predefined, that's not too much work:
class Model < ActiveRecord::Base
STAT_CREATED = 1
STAT_RENEWED = 2
STAT_RETRIEVED = 4
validates_inclusion_of :status,
:in => [1, 2, 4]
def created?
status & STAT_CREATED
end
def renewed?
status & STAT_RENEWED
end
def retrieved?
status & STAT_RETRIEVED
end
end
This way, you could either test the model instance directly (e.g. if #model.created?) or write your case statements like that:
case #model.status
when Model::STAT_CREATED
...
when Model::STAT_RENEWED
...

Also try taking a look at the acts_as_state_machine plugin. I recently used it on a project and it worked well.

Related

Rails Active Record Associations

I've been reading and re-reading the Rails associations guide:
http://guides.rubyonrails.org/association_basics.html
This were close but not quite the same:
Ruby on rails active record associations
I'm not sure how to setup the following scenario.
Events has a status of either pending, open, or close.
I thought this would be simple enough to just have:
event has_one status
status belongs_to event
But this really isn't a one-to-one relationship since a status can belong to many events.
So then I thought I would do something like:
status has_many events
event belongs_to status
But this seems funny, because a status doesn't own an event. An event owns a status, right?
I had tried using enumerations and not have a status model. But that got tricky since it seems like ActiveRecord doesn't really support enumerations. I also figured that having a separate model might be good in case someone wants to expand on the number of options for status, like adding 'awaiting approval' or something.
This post suggests that my latter setup is okay, even though it reads funny:
Really easy Rails Active-Record Associations question
But I'm just wondering if I'm not aware of a better Ruby/Rails way of handling this simple scenario.
Thanks in advance!
Doing this as an active record association is overkill. Think about it, create a whole table just to store 3 values in it that will never change?
What you really need is an enum. But of course, ruby doesn't have an enum.
Luckily, you can fake it.
module StatusCodes
pending = 0
open = 1
closed = 2
end
Then you can do something like this
if #event.status == StatusCodes::open
# do something
end
It's a much simpler solution and your code stays very readable.
Ignore that voice in your head: you're doing it fine. The real important of which model has belongs_to is where the foreign key is stored. It's clear in this example that the foreign key should be stored in the Event model, which means it should belongs_to :status.
I also agree with the other posts, though - if you have got a small and fixed number of potential Status records, consider creating an constant hash to store them instead of creating a whole database table for them.
Why not add a status column to Event (as an integer), and have something like this:
class Event < ActiveRecord::Base
STATUS_TYPES = {1 => "active", 2 => "inactive", 3 => "closed"}
def status
STATUS_TYPES[self[:status]]
end
def status=(new_status)
new_status = STATUS_TYPES.invert[new_status] if new_status.class == "String"
self[:status] = new_status
end
end
You might want to consider using state_machine and a simple string column to implement the status, instead of using an association or a hand-rolled enum.
You are wrong.
If you do
Event
has_one :status
Status
belongs_to :event
Rails will make sure it is a one-to-one association, so status will only belong to one event
I'm pretty sure this is what happens if you try to assign a event status to a different event
e1 = Event.first
status = e1.status
e2 = Event.new
e2.status = status
e2.save
Event.first.status #=> nil

Rails Best Practice for User-Configurable Global Attribute "Defaults"

Sorry about the awkward phrasing of the title -- not quite sure of the best way to title this but here's what I'm seeing assistance with:
In a Rails app, let's say we've got a model for a Product, and one of the attributes of the product is Price.
On the admin side of my app, I'd like to be able to set a "default" price that could be referred to if any new Product created isn't assigned a Price. If a Product does have a value for Price, then it would be used.
This is, of course, an example -- and I ask this question because I've got to imagine this is a common pattern. This can be applied to any resource that might have user-configurable global defaults or resource-specific values.
In pure Ruby, this would be solved, I think, with a class variable, so I'd be able to define ##default_price within the Product class and be able to refer to Product.default_price if the instantiated object's value doesn't exist.
My research here has pointed me towards the rails-settings-cached gem, which would allow for something like MyApp.default_price, but I'm wondering if there's a more elegant (non-plugin) way to accomplish this within the base Rails framework.
Note I'd like to setup this structure in code, but I want to be able to define the actual values through my app (i.e. config files aren't the solution I'm looking for).
Can someone enlighten me with the Rails way of handling this?
ActiveRecord picks up default attribute values from the database schema. However, these are baked into the migration and table schema and not configurable.
If you want configurability, the pattern that I've used is a before_validation callback method to set a value if the attribute is blank, e.g.:
class Product < ActiveRecord::Base
before_validation :set_price_if_blank
validates :price, :presence => true # sanity check in case the default is missing
has_one :price
private
def set_price_if_blank
self.price = Price.default if self.price.blank?
end
end
class Price < ActiveRecord::Base
def self.default
##default ||= Price.where(:default => true).first
end
end
This assumes that your price table is populated with a row that has a default flag. You could achieve this, e.g. through a seeds.rb file. I've added a validation rule to make sure that you still get an error if no default exists. It adds robustness to your application.
Also note that it's best to use Integers or Decimals for price data, not floats. See this answer.

Different model instance representation for controller

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.

Rails 3 - Potential Race Condition?

I have a really simple Rails 3 application where users can reserve one of a finite number of homogeneous items for a particular day. I'm trying to avoid a race condition where two people reserve the last item available on a particular day. The model (simplified) is as follows:
class Reservation < ActiveRecord::Base
belongs_to :user
attr_accessible :date
MAX_THINGS_AVAILABLE = 20
validate :check_things_available
def check_things_available
unless things_available? errors[:base] << "No things available"
end
def things_available?
Reservation.find_all_by_date(date).count < MAX_THINGS_AVAILABLE
end
end
The reservation is being created in the controller via current_user.reservations.build(params[:reservation])
It feels like there is a better way to do this, but I can't quite put my finger on what it is. Any help on how to prevent the race condition would be greatly appreciated.
Not sure this answers your question, but it might point you towards a solution:
http://webcache.googleusercontent.com/search?q=cache:http://barelyenough.org/blog/2007/11/activerecord-race-conditions/
(the original site seems to be down so that's a link to the google cache)
The conclusion on that page is that optimistic locking and row level locking are not solutions for race conditions on create, just on update.
The author suggests reimplementing find_or_create with a db constraint.
Another suggestion is that switching the transaction isolation level to 'serializable' ought to work but there's no information on how to do that in Rails.
Just use any locking mechanism like redis locker
RedisLocker.new("thing_to_sell_#{#thing.id}").run do
current_user.create_reservation(#thing) or raise "Item already sold to another user"
end

Should I prevent record editing using a filter in my controller or a callback in my model?

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?

Resources