ohm-contrib

A collection of drop-in modules for Ohm.

If you're upgrading from 0.1.x, see the changes below

Quick Overview

require 'ohm'
require 'ohm/contrib'

class Post < Ohm::Model
  include Ohm::Timestamps
  include Ohm::DataTypes

  attribute :amount, Type::Decimal
  attribute :url
  attribute :poster_email
  attribute :slug
end

post = Post.create

post.created_at.kind_of?(Time)
# => true

post.update_at.kind_of?(Time)
# => true

It's important to note that created_at and update_at both store times as a unix timestamp for efficiency.

Ohm::Callbacks

Note: Macro-style callbacks have been removed since version 1.0.x. Please use instance style callbacks.

On the topic of callbacks

Since I initially released ohm-contrib, a lot has changed. Initially, I had a bad habit of putting a lot of stuff in callbacks. Nowadays, I prefer having a workflow object or a service layer which coordinates code not really related to the model, a perfect example of which is photo manipulation.

It's best to keep your models pure, and have domain specific code in a separate object.

class Order < Ohm::Model
  attribute :status

  def before_create
    self.status = "pending"
  end

  def after_save
    # do something here
  end
end

Ohm::DataTypes

If you don't already know, Ohm 1.0 already supports typecasting out of the box by taking a lambda parameter. An example best explains:

class Product < Ohm::Model
  attribute :price, lambda { |x| x.to_f }
end

What Ohm::DataTypes does is define all of these lambdas for you, so we don't have to manually define how to cast an Integer, Float, Decimal, Time, Date, etc.

DataTypes: Basic example

class Product < Ohm::Model
  include Ohm::DataTypes

  attribute :price, Type::Decimal
  attribute :start_of_sale, Type::Time
  attribute :end_of_sale, Type::Time
  attribute :priority, Type::Integer
  attribute :rating, Type::Float
end

product = Product.create(price: "127.99")
product.price.kind_of?(BigDecimal)
# => true

product = Product.create(start_of_sale: Time.now.rfc2822)
product.start_of_sale.kind_of?(Time)
# => true

product = Product.create(end_of_sale: Time.now.rfc2822)
product.end_of_sale.kind_of?(Time)
# => true

product = Product.create(priority: "100")
product.priority.kind_of?(Integer)
# => true

product = Product.create(rating: "5.5")
product.rating.kind_of?(Float)
# => true

DataTypes: Advanced example

IMPORTANT NOTE: Mutating a Hash and/or Array doesn't work, so you have to re-assign them accordingly.

class Product < Ohm::Model
  include Ohm::DataTypes

  attribute :meta, Type::Hash
  attribute :sizes, Type::Array
end

product = Product.create(meta: { resolution: '1280x768', battery: '8 hours' },
                         sizes: ['XS S M L XL'])

product.meta.kind_of?(Hash)
# => true

product.meta == { resolution: '1280x768', battery: '8 hours' }
# => true

product.sizes.kind_of?(Array)
# => true

product.sizes == ['XS S M L XL']
# => true

Ohm::Slug

class Post < Ohm::Model
  include Ohm::Slug

  attribute :title

  def to_s
    title
  end
end

post = Post.create(title: "Using Ohm contrib 1.0")
post.to_param == "1-using-ohm-contrib-1.0"
# => true

By default, Ohm::Slug tries to load iconv in order to transliterate non-ascii characters. For ruby 2 or later, you will need to gem install iconv to get transliteration.

post = Post.create(:title => "D├ęcor")
post.to_param == "2-decor"

Ohm::Versioned

For cases where you expect people to be editing long pieces of content concurrently (the most obvious example would be a CMS with multiple moderators), then you need to put some kind of versioning in place.

class Article < Ohm::Model
  include Ohm::Versioned

  attribute :title
  attribute :content
end

a1 = Article.create(:title => "Foo Bar", :content => "Lorem ipsum")
a2 = Article[a1.id]

# At this point, a2 will be stale.
a1.update(title: "Foo Bar Baz")

begin
  a2.update(:title => "Bar Baz")
rescue Ohm::VersionConflict => ex
  ex.attributes == { :title => "Bar Baz", :_version => "1", :content => "Lorem ipsum" }
  # => true
end