chaining.rb |
|
|---|---|
Chaining Ohm Sets |
|
Doing the straight forward approach |
|
|
Let’s design our example around the following requirements:
|
|
Doing it the normal way |
|
|
Let’s first require |
require "ohm" |
|
A
|
class User < Ohm::Model
collection :orders, Order
end |
|
The product for our purposes will only contain a name. |
class Product < Ohm::Model
attribute :name
end |
|
We define an The We also define a |
class Order < Ohm::Model
attribute :state
index :state
reference :user, User
reference :product, Product
end |
Testing what we have so far. |
|
|
For the purposes of this tutorial, we’ll use cutest for our test framework. |
require "cutest" |
|
Make sure that every run of our test suite has a clean Redis instance. |
prepare { Ohm.flush } |
|
Let’s create a user, a pending, authorized and a captured order. We also create two products named iPod and iPad. |
setup do
@user = User.create
@ipod = Product.create(:name => "iPod")
@ipad = Product.create(:name => "iPad")
@pending = Order.create(:user => @user, :state => "pending",
:product => @ipod)
@authorized = Order.create(:user => @user, :state => "authorized",
:product => @ipad)
@captured = Order.create(:user => @user, :state => "captured",
:product => @ipad)
end |
|
Now let’s try and grab all pending orders, and also pending iPad and iPod ones. |
test "finding pending orders" do
assert @user.orders.find(state: "pending").include?(@pending)
assert @user.orders.find(:state => "pending",
:product_id => @ipod.id).include?(@pending)
assert @user.orders.find(:state => "pending",
:product_id => @ipad.id).empty?
end |
|
Now we try and find captured and authorized orders. The tricky part
is trying to find an order that is either captured or authorized,
since |
test "finding authorized and/or captured orders" do
assert @user.orders.find(:state => "authorized").include?(@authorized)
assert @user.orders.find(:state => "captured").include?(@captured)
assert @user.orders.find(:state => ["authorized", "captured"]).empty?
auth_or_capt = @user.orders.key.volatile[:auth_or_capt]
auth_or_capt.sunionstore(
@user.orders.find(:state => "authorized").key,
@user.orders.find(:state => "captured").key
)
assert auth_or_capt.smembers.include?(@authorized.id)
assert auth_or_capt.smembers.include?(@captured.id)
end |
Creating shortcuts |
|
|
You can of course define methods to make that code more readable. |
class User < Ohm::Model
def authorized_orders
orders.find(:state => "authorized")
end
def captured_orders
orders.find(:state => "captured")
end
end |
|
And we can now test these new methods. |
test "finding authorized and/or captured orders" do
assert @user.authorized_orders.include?(@authorized)
assert @user.captured_orders.include?(@captured)
end |
|
In most cases this is fine, but if you want to have a little fun, then we can play around with some chainability. |
|
Chaining Kung-Fu |
|
|
The We can simply subclass it and define the monad to always be an
|
class UserOrders < Ohm::Model::Set
def initialize(key)
super key, Ohm::Model::Wrapper.wrap(Order)
end |
|
Here is the crux of the chaining pattern. Instead of
just doing a straight up |
def pending
self.class.new(model.index_key_for(:state, "pending"))
end
def authorized
self.class.new(model.index_key_for(:state, "authorized"))
end
def captured
self.class.new(model.index_key_for(:state, "captured"))
end |
|
Now we wrap the implementation of doing an NOTE: |
def accepted
model.key.volatile[:accepted].sunionstore(
authorized.key, captured.key
)
self.class.new(model.key.volatile[:accepted])
end
end |
|
Now let’s re-open the |
class User < Ohm::Model
def orders
UserOrders.new(Order.index_key_for(:user_id, id))
end
end |
|
Ok! Let’s put all of that chaining code to good use. |
test "finding pending orders using a chainable style" do
assert @user.orders.pending.include?(@pending)
assert @user.orders.pending.find(:product_id => @ipod.id).include?(@pending)
assert @user.orders.pending.find(:product_id => @ipad.id).empty?
end
test "finding authorized and/or captured orders using a chainable style" do
assert @user.orders.authorized.include?(@authorized)
assert @user.orders.captured.include?(@captured)
assert @user.orders.accepted.include?(@authorized)
assert @user.orders.accepted.include?(@captured)
accepted = @user.orders.accepted
assert accepted.find(:product_id => @ipad.id).include?(@authorized)
assert accepted.find(:product_id => @ipad.id).include?(@captured)
end |
Conclusion |
|
|
This design pattern is something that really depends upon the situation. In
the example above, you can add more complicated querying on the The most important takeaway here is the ease of which we can weild the different components of Ohm, and mold it accordingly to our preferences, without having to monkey-patch anything. |
|