Skip to content

Instantly share code, notes, and snippets.

@jsmestad
Created May 26, 2011 20:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jsmestad/993978 to your computer and use it in GitHub Desktop.
Save jsmestad/993978 to your computer and use it in GitHub Desktop.
Proposed changes for Mongomatic 0.9.0
@snapshot = Snapshot.first
-----
{
:stats => {
'accounts' => { 'count' => 8 },
'subscriptions' => { 'count' => 16 }
},
:activity => [ {'ns' => 'accounts.users', ...}, {'ns' => 'subscriptions.users' =>, ...} ]
}
-----
# Attribute definitions would be defined as instance method accessors
#
# For example:
# def database_id; self.doc[:database_id]; end
@snapshot.database_id
@snapshot.created_at
@snapshot.stats
----
@snapshot.select('stats.*', :count => 16) # would search all sub-hashes that match the query, returning the hash containing the value
@snapshot.select('activity.*', :ns => /^accounts\.$/) # would return the sub-hash that matches the query in the array
-----
@snapshot['stats.accounts.count'] # => 8
@snapshot['stats.posts.count'] # => nil
@snapshot.doc(['stats.posts.count'], true|false) # => KeyDoesNotExist, 'stats.posts does not exist in the document hash, true is the default here as your assuming the hash exists at the doc level
# Assignment -- not sure about this one.
@snapshot['stats.posts.count'] = 8 # => {:stats => { 'posts' => { 'count' => 8 } } } but raise error if the type is an Array.
@snapshot['stats.*.count'] = 8 # => mass assignment to doc[:stats].each {|x| x[:count] = 8 }
class Snapshot
include Mongomatic # this will replace Mongomatic::Base, its like saying 'normal defaults'
include Mongomatic::Sanitize
attribute :database_id, :typed => Integer, :required => true
attribute :created_at, :typed => Time, :required => true
attribute :stats, :typed => Hash, :required => true
sanitize_keys # will do the entire document
sanitize_keys :stats, :replace_with => '#', :depth => 2 # various options we could support
indexes do |collection|
collection.ensure_index([['database_id', Mongo::ASCENDING], ['created_at', Mongo::DESCENDING]], :unique => true)
end
end
@jsmestad
Copy link
Author

== Notes

  • Deprecate / Remove validation and expectation syntax in favor of using callbacks (back to minimalism)
  • Sanitize keys will work around errors with keys containing reserved MongoDB reserved key characters like '.'
  • Unsure if indexes syntax will be evaluated on load, probably not. This will just standardize how they get defined.
  • Moving Mongomatic to a module instead of a base class will allow for ActiveResource/ActiveRecord/etc to be utilized at the same time as Mongomatic (disregarding naming conflicts)
  • Move toward adopting the practice of using ActiveModel / ActiveResource where possible to minimize the possibility to technological debt moving forward (keep it lean).

@jrwest
Copy link

jrwest commented May 26, 2011

Comments on First Glance (More to Come):

  • Moving expectations and typed fields into the single "attribute" class method is a huge improvement
  • Sanitize is fine by me, its a little unclear what its doing without context of our conversation earlier though (we would just need to do a good job explaining this functionality in the README/docs).
  • I don't think indexes should be created on load, but this should essentially write a Snapshot.create_indexes function for you which we can build into a mongomatic/cap module for Capfile.
  • Now that I see it, I do like including Mongomatic as a module instead of inheriting. It make sense semantically, the whole "now you have the base stuff, include other modules to get more". Plus, the other advantages you point out.

@jrwest
Copy link

jrwest commented May 26, 2011

@jrwest
Copy link

jrwest commented May 26, 2011

Made some proposed changes to Justin's proposal here: https://gist.github.com/994253

  • #select always returns an array, its probably because I've been doing more FP with Erlang recently, but I feel like its more proper that way, since its possible more than one sub-hash can match. Also, then you get the benefit of all the Enumerable-like functions we can write on top of it
  • Not sure if I like it, but added an "index" class method
  • Example of modifier chaining in game_object.rb

@jrwest
Copy link

jrwest commented May 31, 2011

Began implementing the attributes DSL. One thing I'm unsure of so far though I could use some input on:

The old typed field DSL did casting and checking on validate. My implementation does checking and casting on setting of a value (either when initializing or via #set_value_for_key or #[]=). The first approach makes it possible to be working with a Mongomatic object whose values haven't been casted or checked, which I don't care for much (but I guess could be mitigated by casting or checking on read but I think it would feel odd to get an error when getting a value from a Mongomatic document). However, the second approach means that errors can be thrown when reading values from the database, if another entity modifies a value, (since initialize will throw an error for an invalid type that either can't be cast or was explicitly set to not be casted) and this must be handled by the application. Would be interested to hear thoughts on preferred approach. I think I prefer the cast/check on set since that's what I implemented but its worth discussing IMO.

EDIT: should note for context, with what I have so far, required field checking is still done on validate. It is only type checking and casting I'm considering above.

@jsmestad
Copy link
Author

Two use-cases we could address with this:

1.) Object Serialization -- So I think if you want casting, it should be done bi-directionally. This way you can have something that is an object (think YAML serialization) and then cast it back as an object of specified Class when its pulled out.

2.) DateTime/Time/Date objects -- Again bi-directional is useful if you have something where you save a Date.today, and when you pull it back out, it should still be only a Date object and not the MongoDB Time object (and not UTC for example).

The rest of it is just verbose crap since the mongodb ruby driver handles converting of standard Ruby formats. Maybe we should change the syntax to something like...

attribute :snapshots, :typed => Proc.new { |obj| obj.collect {|o| Snapshot.new(o)} }
attribute :created_at, :typed => Proc.new { |obj| Date.new(obj.year, obj.month, obj.day) }

This way you can do @record.snapshots.class # => [<#Snapshot>,<#Snapshot>,....]

@jrwest
Copy link

jrwest commented Jun 1, 2011

I think object serialization and handling of DateTime/etc objects are something we should address for sure (actually there was a lot of discussing on how to do the latter at the Ruby talk at MongoSF). However, I do think I was addressing a third use case with this: handling data from forms. The mongo driver casts to the correct mongo type but it doesn't cast "1.23" to its float equivalent, etc. If you have a lot of things like checkboxes or textfields to take numbers having some "type casting" like that can reduce/simplify code. IMO, useful when writing something like an admin tool and I think making writing admin screens for Shadelight easier was one of the reasons Ben built typed fields. Also, its not hard to build your own Mongomatic::TypeConverters and have custom "casters", which could lead to some cool stuff. But like I said before, I'm not dead set on implementing it this way, it just happens to be how I was first inclined to build and use it. We can always rip it out and rewrite it with a focus solely on serialization.

For example something like:

 # some rails action
 def my_action
      item_instance = Item.new # some Mongomatic model instance
      item_instance['avail_for_purchase'] = params[:item][:avail_for_purchase] == "1" ? 1 : 0
      item_instance['price'] = params[:item][:price].to_f # lets just hope in this example we get a proper dollar value
      # lots more control statements & setting of values
      #...
 end

just becomes:

 def my_action
    item_instance.new(params[:item])
 end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment