-
-
Save duff/649855 to your computer and use it in GitHub Desktop.
# An account has payment methods. I'd like to know the payment methods for an account | |
# which meet some criteria. | |
# The 2 models | |
class Account | |
include Ripple::Document | |
many :payment_methods | |
end | |
class PaymentMethod | |
include Ripple::Document | |
one :account | |
property :storage_state, String | |
end | |
# In Active Record I can do the following: | |
account = Account.first | |
account.payment_methods.retained | |
# since there can be a named scope such as: | |
class PaymentMethod | |
scope :retained, :conditions => [ "storage_state = ?", "retained" ] | |
end | |
# I'm wondering what the best way might be to do this in Riak. |
That's excellent. I incorporated your suggestions.
module Ripple
module Document
module ClassMethods
def objects_from(map_reduce)
Riak::RObject.load_from_mapreduce(Ripple.client, map_reduce.run).map {|obj| instantiate(obj) }
end
end
end
end
class Account
def retained_payment_methods
mr = Riak::MapReduce.new(Ripple.client).
add(self.class.bucket_name, self.key).
link(:bucket => PaymentMethod.bucket_name).
map("Ripple.mapKeysByFields", :arg => {:storage_state => "retained"}).
map("Ripple.mapIdentity", :keep => true)
PaymentMethod.objects_from(mr)
end
end
Let's explore how to make this generic. If we exposed map and reduce as methods that resemble the "scoping" methods on ActiveRecord, this could work out well...
# Defaults to full-bucket query
Account.map(...)...
# Start from the object
account.link(:bucket => "payment_methods").map("Ripple.mapKeysByFields", :arg => {:storage_state => "retained"}).map("Ripple.mapIdentity", :keep => true)
# Or even more abstractly:
account.payment_methods.filter(:storage_state => "retained").to_docs
Filter would determine which map function to use (the simple equality, or the complex conditions), and to_docs (probably aliased as to_a so we can get lazy-loading) would make sure you have the mapIdentity and then invoke the job. Obviously association proxies should be overloaded to create a query that starts with a link phase when we call one of the query methods on them.
Pretty interesting ideas Sean. I'll see if I can incorporate some of them into my stuff here and see where it leads. Getting pretty excited about the possibilities.
It appears that this line:
map("Ripple.mapKeysByFields", :arg => {:storage_state => "retained"})
Is equivalent to:
map("Ripple.filterByConditions", :arg => {:storage_state => { "==" => "retained" }})
I'm now looking into the best way to do something like this for Times:
class Account
include Ripple::Document
property :charged_for_storage_at, Time
end
map("Ripple.filterByConditions", :arg => {:charged_for_storage_at => { "<" => 1.month.ago }})
I need to look at Ripple and determine how Times are represented and what I need to pass in rather than (1.month.ago).
Currently it uses the format that is best supported by Date.parse in Javascript. My experience in the past has been that Unix UTC timestamps have the best reliability, but I'm open to suggestions.
OK. Got it working! Now it's time to clean it up.
Thanks Sean!
Can I expect a pull request from you soon? hint hint
We'll see! I've been a bit surprised how little querying I've needed to do for the app I'm working on. At this point, I'm in "get some things working" mode. I'm now querying for strings and dates. I don't yet have enough real uses of querying in real apps to have a worthy pull request ready. I would think that would change over time. :)
The advantage of having the "mapIdentity" phase is that the objects are returned to you in the query and you don't need to fetch them separately. You will however, have to instantiate them as documents:
PaymentMethod::instantiate might be non-public.