mongodb_model 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/mongodb_model/assignment.rb +65 -0
- data/lib/mongodb_model/attribute_convertors.rb +54 -0
- data/lib/mongodb_model/callbacks.rb +36 -0
- data/lib/mongodb_model/crud.rb +57 -0
- data/lib/mongodb_model/db.rb +53 -0
- data/lib/mongodb_model/gems.rb +7 -0
- data/lib/mongodb_model/misc.rb +33 -0
- data/lib/mongodb_model/model.rb +11 -0
- data/lib/mongodb_model/query.rb +36 -0
- data/lib/mongodb_model/scope.rb +99 -0
- data/lib/mongodb_model/spec.rb +12 -0
- data/lib/mongodb_model/support/types.rb +110 -0
- data/lib/mongodb_model/validation.rb +5 -0
- data/lib/mongodb_model.rb +36 -0
- data/readme.md +72 -0
- data/spec/assignment_spec.rb +80 -0
- data/spec/attribute_convertors_spec.rb +73 -0
- data/spec/callbacks_spec.rb +47 -0
- data/spec/crud_spec.rb +151 -0
- data/spec/db_spec.rb +63 -0
- data/spec/misc_spec.rb +58 -0
- data/spec/query_spec.rb +46 -0
- data/spec/scope_spec.rb +149 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/validatable2_spec.rb +40 -0
- data/spec/validation_spec.rb +37 -0
- metadata +71 -2
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            module Mongo::Model::Assignment
         | 
| 2 | 
            +
              class Dsl < BasicObject
         | 
| 3 | 
            +
                def initialize
         | 
| 4 | 
            +
                  @attributes = {}
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def self.const_missing name
         | 
| 8 | 
            +
                  # BasicObject doesn't have access to any constants like String, Symbol, ...
         | 
| 9 | 
            +
                  ::Object.const_get name
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def to_h; attributes end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                protected
         | 
| 15 | 
            +
                  attr_reader :attributes
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def method_missing attribute_name, *args
         | 
| 18 | 
            +
                    attribute_name.must_be.a Symbol
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    args.size.must_be.in 1..2
         | 
| 21 | 
            +
                    if args.first.is_a? Class
         | 
| 22 | 
            +
                      type, mass_assignment = args
         | 
| 23 | 
            +
                      mass_assignment ||= false
         | 
| 24 | 
            +
                      type.must.respond_to :cast
         | 
| 25 | 
            +
                    else
         | 
| 26 | 
            +
                      type, mass_assignment = nil, args.first
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    attributes[attribute_name] = [type, mass_assignment]
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def set attributes, options = {}
         | 
| 34 | 
            +
                if rules = self.class._assign
         | 
| 35 | 
            +
                  force = options[:force]
         | 
| 36 | 
            +
                  attributes.each do |n, v|
         | 
| 37 | 
            +
                    n = n.to_sym
         | 
| 38 | 
            +
                    if rule = rules[n]
         | 
| 39 | 
            +
                      type, mass_assignment = rule
         | 
| 40 | 
            +
                      if mass_assignment or force
         | 
| 41 | 
            +
                        v = type.cast(v) if type
         | 
| 42 | 
            +
                        send "#{n}=", v
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                else
         | 
| 47 | 
            +
                  attributes.each{|n, v| send "#{n}=", v}
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
                self
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def set! attributes, options = {}
         | 
| 53 | 
            +
                set attributes, options.merge(force: true)
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              module ClassMethods
         | 
| 57 | 
            +
                inheritable_accessor :_assign, nil
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def assign &block
         | 
| 60 | 
            +
                  dsl = ::Mongo::Model::Assignment::Dsl.new
         | 
| 61 | 
            +
                  dsl.instance_eval &block
         | 
| 62 | 
            +
                  self._assign = (_assign || {}).merge dsl.to_h
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
            require 'yaml'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Mongo::Model::AttributeConvertors
         | 
| 5 | 
            +
              CONVERTORS = {
         | 
| 6 | 
            +
                line: {
         | 
| 7 | 
            +
                  from_string: -> s {(s || "").split(',').collect{|s| s.strip}},
         | 
| 8 | 
            +
                  to_string:   -> v {v.join(', ')}
         | 
| 9 | 
            +
                },
         | 
| 10 | 
            +
                column: {
         | 
| 11 | 
            +
                  from_string: -> s {(s || "").split("\n").collect{|s| s.strip}},
         | 
| 12 | 
            +
                  to_string:   -> v {v.join("\n")}
         | 
| 13 | 
            +
                },
         | 
| 14 | 
            +
                yaml: {
         | 
| 15 | 
            +
                  from_string: -> s {YAML.load s rescue {}},
         | 
| 16 | 
            +
                  to_string:   -> v {v.to_yaml.strip}
         | 
| 17 | 
            +
                },
         | 
| 18 | 
            +
                json: {
         | 
| 19 | 
            +
                  from_string: -> s {JSON.parse s rescue {}},
         | 
| 20 | 
            +
                  to_string:   -> v {v.to_json.strip}
         | 
| 21 | 
            +
                }
         | 
| 22 | 
            +
              }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              module ClassMethods
         | 
| 25 | 
            +
                def available_as_string name, converter_name
         | 
| 26 | 
            +
                  converter = CONVERTORS[converter_name]
         | 
| 27 | 
            +
                  raise "unknown converter name :#{converter_name} for :#{name} field!" unless converter
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  from_string, to_string = converter[:from_string], converter[:to_string]
         | 
| 30 | 
            +
                  name_as_string = "#{name}_as_string".to_sym
         | 
| 31 | 
            +
                  define_method name_as_string do
         | 
| 32 | 
            +
                    _cache[name_as_string] ||= to_string.call(send(name))
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  define_method "#{name_as_string}=" do |value|
         | 
| 36 | 
            +
                    _cache.delete name_as_string
         | 
| 37 | 
            +
                    self.send "#{name}=", from_string.call(value)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def available_as_yaml name
         | 
| 42 | 
            +
                  raise "delimiter not specified for :#{name} field!" unless delimiter
         | 
| 43 | 
            +
                  method = "#{name}_as_string"
         | 
| 44 | 
            +
                  define_method method do
         | 
| 45 | 
            +
                    self.send(name).join(delimiter)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  define_method "#{method}=" do |value|
         | 
| 48 | 
            +
                    value = (value || "").split(delimiter.strip).collect{|s| s.strip}
         | 
| 49 | 
            +
                    self.send "#{name}=", value
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            module Mongo::Model::Callbacks
         | 
| 2 | 
            +
              inherit RubyExt::Callbacks
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              def _run_callbacks type, method_name
         | 
| 5 | 
            +
                if type == :before
         | 
| 6 | 
            +
                  run_before_callbacks method_name, method: method_name
         | 
| 7 | 
            +
                elsif type == :after
         | 
| 8 | 
            +
                  run_after_callbacks method_name, method: method_name
         | 
| 9 | 
            +
                else
         | 
| 10 | 
            +
                  raise "invalid callback type (#{type})!"
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              module ClassMethods
         | 
| 15 | 
            +
                [:validate, :update, :save, :destroy].each do |method_name|
         | 
| 16 | 
            +
                  define_method "before_#{method_name}" do |*args, &block|
         | 
| 17 | 
            +
                    opt = args.extract_options!
         | 
| 18 | 
            +
                    if block
         | 
| 19 | 
            +
                      set_callback method_name, :before, opt, &block
         | 
| 20 | 
            +
                    else
         | 
| 21 | 
            +
                      opt[:terminator] = false unless opt.include? :terminator
         | 
| 22 | 
            +
                      args.each{|executor| set_callback method_name, :before, executor, opt}
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  define_method "after_#{method_name}" do |*args, &block|
         | 
| 27 | 
            +
                    opt = args.extract_options!
         | 
| 28 | 
            +
                    if block
         | 
| 29 | 
            +
                      set_callback method_name, :after, opt, &block
         | 
| 30 | 
            +
                    else
         | 
| 31 | 
            +
                      args.each{|executor| set_callback method_name, :after, executor, opt}
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            module Mongo::Model::Crud
         | 
| 2 | 
            +
              def save opts = {}
         | 
| 3 | 
            +
                with_collection opts do |collection, opts|
         | 
| 4 | 
            +
                  collection.save self, opts
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def save! *args
         | 
| 9 | 
            +
                save(*args) || raise(Mongo::Error, "can't save #{self.inspect}!")
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def destroy opts = {}
         | 
| 13 | 
            +
                with_collection opts do |collection, opts|
         | 
| 14 | 
            +
                  collection.destroy self, opts
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def destroy! *args
         | 
| 19 | 
            +
                destroy(*args) || raise(Mongo::Error, "can't destroy #{self.inspect}!")
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              module ClassMethods
         | 
| 23 | 
            +
                def build attributes, opts = {}
         | 
| 24 | 
            +
                  self.new.set attributes, opts
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def create attributes, opts = {}
         | 
| 28 | 
            +
                  o = build attributes, opts
         | 
| 29 | 
            +
                  o.save
         | 
| 30 | 
            +
                  o
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def create! attributes, opts = {}
         | 
| 34 | 
            +
                  o = create attributes
         | 
| 35 | 
            +
                  raise(Mongo::Error, "can't create #{attributes.inspect}!") if o.new_record?
         | 
| 36 | 
            +
                  o
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def destroy_all selector = {}, opts = {}
         | 
| 40 | 
            +
                  success = true
         | 
| 41 | 
            +
                  collection = opts[:collection] || self.collection
         | 
| 42 | 
            +
                  each(selector){|o| success = false unless o.destroy}
         | 
| 43 | 
            +
                  success
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def destroy_all! selector = {}, opts = {}
         | 
| 47 | 
            +
                  destroy_all(selector, opts) || raise(Mongo::Error, "can't destroy #{selector.inspect}!")
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              protected
         | 
| 52 | 
            +
                def with_collection opts, &block
         | 
| 53 | 
            +
                  opts = opts.clone
         | 
| 54 | 
            +
                  collection = opts.delete(:collection) || self.class.collection
         | 
| 55 | 
            +
                  block.call collection, opts
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            module Mongo::Model::Db
         | 
| 2 | 
            +
              module ClassMethods
         | 
| 3 | 
            +
                inheritable_accessor :_db, nil
         | 
| 4 | 
            +
                def db= v
         | 
| 5 | 
            +
                  self._db = if v.is_a? ::Proc
         | 
| 6 | 
            +
                    v
         | 
| 7 | 
            +
                  elsif v.is_a? ::Symbol
         | 
| 8 | 
            +
                    -> {::Mongo::Model.connection.db v.to_s}
         | 
| 9 | 
            +
                  else
         | 
| 10 | 
            +
                    -> {v}
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def db *args, &block
         | 
| 15 | 
            +
                  if block
         | 
| 16 | 
            +
                    self.db = block
         | 
| 17 | 
            +
                  elsif !args.empty?
         | 
| 18 | 
            +
                    args.size.must == 1
         | 
| 19 | 
            +
                    self.db = args.first
         | 
| 20 | 
            +
                  else
         | 
| 21 | 
            +
                    (_db && _db.call) || ::Mongo::Model.db
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                inheritable_accessor :_collection, nil
         | 
| 26 | 
            +
                def collection= v
         | 
| 27 | 
            +
                  self._collection = if v.is_a? ::Proc
         | 
| 28 | 
            +
                    v
         | 
| 29 | 
            +
                  elsif v.is_a? ::Symbol
         | 
| 30 | 
            +
                    -> {db.collection v}
         | 
| 31 | 
            +
                  else
         | 
| 32 | 
            +
                    -> {v}
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def collection *args, &block
         | 
| 37 | 
            +
                  if block
         | 
| 38 | 
            +
                    self.collection = block
         | 
| 39 | 
            +
                  elsif !args.empty?
         | 
| 40 | 
            +
                    args.size.must == 1
         | 
| 41 | 
            +
                    self.collection = args.first
         | 
| 42 | 
            +
                  else
         | 
| 43 | 
            +
                    (_collection && _collection.call) || db.collection(default_collection_name)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def default_collection_name
         | 
| 48 | 
            +
                  first_ancestor_class = ancestors.find{|a| a.is_a? Class} ||
         | 
| 49 | 
            +
                    raise("can't evaluate default collection name for #{self}!")
         | 
| 50 | 
            +
                  first_ancestor_class.alias.pluralize.underscore.to_sym
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Mongo::Model::Misc
         | 
| 2 | 
            +
              def update_timestamps
         | 
| 3 | 
            +
                now = Time.now.utc
         | 
| 4 | 
            +
                self.created_at ||= now
         | 
| 5 | 
            +
                self.updated_at = now
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
              def _cache
         | 
| 10 | 
            +
                @_cache ||= {}
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
              def _clear_cache
         | 
| 13 | 
            +
                @_cache = {}
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
             | 
| 17 | 
            +
              def dom_id
         | 
| 18 | 
            +
                # new_record? ? "new_#{self.class.name.underscore}" : to_param
         | 
| 19 | 
            +
                to_param
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def to_param
         | 
| 23 | 
            +
                (_id || '').to_s
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
             | 
| 27 | 
            +
              module ClassMethods
         | 
| 28 | 
            +
                def timestamps!
         | 
| 29 | 
            +
                  attr_accessor :created_at, :updated_at
         | 
| 30 | 
            +
                  before_save :update_timestamps
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            module Mongo::Model::Query
         | 
| 2 | 
            +
              module ClassMethods
         | 
| 3 | 
            +
                include Mongo::DynamicFinders
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def count selector = {}, opts = {}
         | 
| 6 | 
            +
                  collection.count selector, opts
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def first selector = {}, opts = {}
         | 
| 10 | 
            +
                  collection.first selector, opts
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def each selector = {}, opts = {}, &block
         | 
| 14 | 
            +
                  collection.each selector, opts, &block
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def all selector = {}, opts = {}, &block
         | 
| 18 | 
            +
                  if block
         | 
| 19 | 
            +
                    each selector, opts, &block
         | 
| 20 | 
            +
                  else
         | 
| 21 | 
            +
                    list = []
         | 
| 22 | 
            +
                    each(selector, opts){|doc| list << doc}
         | 
| 23 | 
            +
                    list
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def first! selector = {}, opts = {}
         | 
| 28 | 
            +
                  first(selector, opts) || raise(Mongo::NotFound, "document with selector #{selector} not found!")
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def exists? selector = {}, opts = {}
         | 
| 32 | 
            +
                  count(selector, opts) > 0
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
                alias :exist? :exists?
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            module Mongo::Model::Scope
         | 
| 2 | 
            +
              class ScopeProxy < BasicObject
         | 
| 3 | 
            +
                def initialize model, scope
         | 
| 4 | 
            +
                  @model, @scope = model, scope
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def class
         | 
| 8 | 
            +
                  ::Mongo::Model::Scope::ScopeProxy
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def reverse_merge! scope
         | 
| 12 | 
            +
                  @scope = scope.merge @scope
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def inspect
         | 
| 16 | 
            +
                  "#<ScopeProxy:{#{scope.inspect}}>"
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                alias_method :to_s, :inspect
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                protected
         | 
| 21 | 
            +
                  attr_reader :model, :scope
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def method_missing method, *args, &block
         | 
| 24 | 
            +
                    model.with_scope scope do
         | 
| 25 | 
            +
                      result = model.send method, *args, &block
         | 
| 26 | 
            +
                      result.reverse_merge! scope if result.class == ::Mongo::Model::Scope::ScopeProxy
         | 
| 27 | 
            +
                      result
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              module ClassMethods
         | 
| 33 | 
            +
                def current_scope
         | 
| 34 | 
            +
                  scope, exclusive = Thread.current[:mongo_model_scope]
         | 
| 35 | 
            +
                  if exclusive
         | 
| 36 | 
            +
                    scope
         | 
| 37 | 
            +
                  elsif scope
         | 
| 38 | 
            +
                    default_scope.merge scope
         | 
| 39 | 
            +
                  else
         | 
| 40 | 
            +
                    default_scope
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def with_exclusive_scope options = {}, &block
         | 
| 45 | 
            +
                  with_scope options, true, &block
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def with_scope options = {}, exclusive = false, &block
         | 
| 49 | 
            +
                  previous_options, previous_exclusive = Thread.current[:mongo_model_scope]
         | 
| 50 | 
            +
                  raise "exclusive scope already applied!" if previous_exclusive
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  begin
         | 
| 53 | 
            +
                    options = previous_options.merge options if previous_options and !exclusive
         | 
| 54 | 
            +
                    Thread.current[:mongo_model_scope] = [options, exclusive]
         | 
| 55 | 
            +
                    return block.call
         | 
| 56 | 
            +
                  ensure
         | 
| 57 | 
            +
                    Thread.current[:mongo_model_scope] = [previous_options, false]
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                inheritable_accessor :_default_scope, -> {{}}
         | 
| 62 | 
            +
                def default_scope *args, &block
         | 
| 63 | 
            +
                  if block
         | 
| 64 | 
            +
                    self._default_scope = block
         | 
| 65 | 
            +
                  elsif !args.empty?
         | 
| 66 | 
            +
                    args.size.must == 1
         | 
| 67 | 
            +
                    args.first.must_be.a Hash
         | 
| 68 | 
            +
                    scope = args.first
         | 
| 69 | 
            +
                    self._default_scope = -> {args.first}
         | 
| 70 | 
            +
                  else
         | 
| 71 | 
            +
                    _default_scope.call
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def scope name, options = nil, &block
         | 
| 76 | 
            +
                  model = self
         | 
| 77 | 
            +
                  metaclass.define_method name do
         | 
| 78 | 
            +
                    scope = (block && block.call) || options
         | 
| 79 | 
            +
                    ScopeProxy.new model, scope
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
             | 
| 84 | 
            +
                #
         | 
| 85 | 
            +
                # finders
         | 
| 86 | 
            +
                #
         | 
| 87 | 
            +
                def count selector = {}, opts = {}
         | 
| 88 | 
            +
                  super current_scope.merge(selector), opts
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def first selector = {}, opts = {}
         | 
| 92 | 
            +
                  super current_scope.merge(selector), opts
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def each selector = {}, opts = {}, &block
         | 
| 96 | 
            +
                  super current_scope.merge(selector), opts, &block
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
            end
         | 
| @@ -0,0 +1,110 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Boolean
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            module Mongo::Model::BooleanType
         | 
| 5 | 
            +
              Mapping = {
         | 
| 6 | 
            +
                true    => true,
         | 
| 7 | 
            +
                'true'  => true,
         | 
| 8 | 
            +
                'TRUE'  => true,
         | 
| 9 | 
            +
                'True'  => true,
         | 
| 10 | 
            +
                't'     => true,
         | 
| 11 | 
            +
                'T'     => true,
         | 
| 12 | 
            +
                '1'     => true,
         | 
| 13 | 
            +
                1       => true,
         | 
| 14 | 
            +
                1.0     => true,
         | 
| 15 | 
            +
                false   => false,
         | 
| 16 | 
            +
                'false' => false,
         | 
| 17 | 
            +
                'FALSE' => false,
         | 
| 18 | 
            +
                'False' => false,
         | 
| 19 | 
            +
                'f'     => false,
         | 
| 20 | 
            +
                'F'     => false,
         | 
| 21 | 
            +
                '0'     => false,
         | 
| 22 | 
            +
                0       => false,
         | 
| 23 | 
            +
                0.0     => false,
         | 
| 24 | 
            +
                nil     => nil
         | 
| 25 | 
            +
              }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              def cast value
         | 
| 28 | 
            +
                if value.is_a? Boolean
         | 
| 29 | 
            +
                  value
         | 
| 30 | 
            +
                else
         | 
| 31 | 
            +
                  Mapping[value] || false
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            class Boolean; end unless defined?(Boolean)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Boolean.extend Mongo::Model::BooleanType
         | 
| 39 | 
            +
             | 
| 40 | 
            +
             | 
| 41 | 
            +
            #
         | 
| 42 | 
            +
            # Date
         | 
| 43 | 
            +
            #
         | 
| 44 | 
            +
            require 'date'
         | 
| 45 | 
            +
            Date.class_eval do
         | 
| 46 | 
            +
              def self.cast value
         | 
| 47 | 
            +
                if value.nil? || value == ''
         | 
| 48 | 
            +
                  nil
         | 
| 49 | 
            +
                else
         | 
| 50 | 
            +
                  date = value.is_a?(::Date) || value.is_a?(::Time) ? value : ::Date.parse(value.to_s)
         | 
| 51 | 
            +
                  date.to_date
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              rescue
         | 
| 54 | 
            +
                nil
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
             | 
| 59 | 
            +
            #
         | 
| 60 | 
            +
            # Float
         | 
| 61 | 
            +
            #
         | 
| 62 | 
            +
            Float.class_eval do
         | 
| 63 | 
            +
              def self.cast value
         | 
| 64 | 
            +
                value.nil? ? nil : value.to_f
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
             | 
| 69 | 
            +
            #
         | 
| 70 | 
            +
            # Integer
         | 
| 71 | 
            +
            #
         | 
| 72 | 
            +
            Integer.class_eval do
         | 
| 73 | 
            +
              def self.cast value
         | 
| 74 | 
            +
                value_to_i = value.to_i
         | 
| 75 | 
            +
                if value_to_i == 0 && value != value_to_i
         | 
| 76 | 
            +
                  value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
         | 
| 77 | 
            +
                else
         | 
| 78 | 
            +
                  value_to_i
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
            end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
             | 
| 84 | 
            +
            #
         | 
| 85 | 
            +
            # String
         | 
| 86 | 
            +
            #
         | 
| 87 | 
            +
            String.class_eval do
         | 
| 88 | 
            +
              def self.cast value
         | 
| 89 | 
            +
                value.nil? ? nil : value.to_s
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
            end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
             | 
| 94 | 
            +
            #
         | 
| 95 | 
            +
            # Time
         | 
| 96 | 
            +
            #
         | 
| 97 | 
            +
            Time.class_eval do
         | 
| 98 | 
            +
              def self.cast value
         | 
| 99 | 
            +
                if value.nil? || value == ''
         | 
| 100 | 
            +
                  nil
         | 
| 101 | 
            +
                else
         | 
| 102 | 
            +
                  # time_class = ::Time.try(:zone).present? ? ::Time.zone : ::Time
         | 
| 103 | 
            +
                  # time = value.is_a?(::Time) ? value : time_class.parse(value.to_s)
         | 
| 104 | 
            +
                  # strip milliseconds as Ruby does micro and bson does milli and rounding rounded wrong
         | 
| 105 | 
            +
                  # at(time.to_i).utc if time
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  value.is_a?(::Time) ? value : Date.parse(value.to_s).to_time
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
              end
         | 
| 110 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            require 'mongodb_model/gems'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'validatable'
         | 
| 4 | 
            +
            require 'i18n'
         | 
| 5 | 
            +
            require 'ruby_ext'
         | 
| 6 | 
            +
            require 'mongodb/object'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Mongo::Model; end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            %w(
         | 
| 11 | 
            +
              support/types
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              db
         | 
| 14 | 
            +
              assignment
         | 
| 15 | 
            +
              callbacks
         | 
| 16 | 
            +
              validation
         | 
| 17 | 
            +
              crud
         | 
| 18 | 
            +
              query
         | 
| 19 | 
            +
              scope
         | 
| 20 | 
            +
              attribute_convertors
         | 
| 21 | 
            +
              misc
         | 
| 22 | 
            +
              model
         | 
| 23 | 
            +
            ).each{|f| require "mongodb_model/#{f}"}
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            module Mongo
         | 
| 26 | 
            +
              module Model
         | 
| 27 | 
            +
                inherit Db, Assignment, Callbacks, Validation, Crud, Query, Scope, AttributeConvertors, Misc
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            Mongo.defaults.merge! \
         | 
| 32 | 
            +
              symbolize:                    true,
         | 
| 33 | 
            +
              convert_underscore_to_dollar: true,
         | 
| 34 | 
            +
              batch_size:                   50,
         | 
| 35 | 
            +
              multi:                        true,
         | 
| 36 | 
            +
              safe:                         true
         | 
    
        data/readme.md
    CHANGED
    
    | @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            Object Model for MongoDB (callbacks, validations, mass-assignment, finders, ...).
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            - The same API for pure driver and Models.
         | 
| 4 | 
            +
            - Minimum extra abstractions, trying to keep things as close to the MongoDB semantic as possible.
         | 
| 5 | 
            +
            - Schema-less, dynamic (with ability to specify types for mass-assignment).
         | 
| 6 | 
            +
            - Models can be saved to any collection.
         | 
| 7 | 
            +
            - Full support for embedded objects (validations, callbacks, ...).
         | 
| 8 | 
            +
            - Scope, default_scope
         | 
| 9 | 
            +
            - Doesn't try to mimic ActiveRecord, MongoDB is differrent and this tool designed to get most of it.
         | 
| 10 | 
            +
            - Very small, see [code stats][code_stats].
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Other ODM usually try to cover simple but non-standard API of MongoDB behind complex ORM-like abstractions. This tool **exposes simplicity and power of MongoDB and leverages it's differences**.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ``` ruby
         | 
| 15 | 
            +
            # Connecting to MongoDB.
         | 
| 16 | 
            +
            require 'mongodb/model'
         | 
| 17 | 
            +
            Mongo.defaults.merge! symbolize: true, multi: true, safe: true
         | 
| 18 | 
            +
            connection = Mongo::Connection.new
         | 
| 19 | 
            +
            db = connection.db 'default_test'
         | 
| 20 | 
            +
            db.units.drop
         | 
| 21 | 
            +
            Mongo::Model.db = db
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            # Let's define the game unit.
         | 
| 24 | 
            +
            class Unit
         | 
| 25 | 
            +
              inherit Mongo::Model
         | 
| 26 | 
            +
              collection :units
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              attr_accessor :name, :status, :stats
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              scope :alive, status: 'alive'
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              class Stats
         | 
| 33 | 
            +
                inherit Mongo::Model
         | 
| 34 | 
            +
                attr_accessor :attack, :life, :shield
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            # Create.
         | 
| 39 | 
            +
            zeratul  = Unit.build(name: 'Zeratul',  status: 'alive', stats: Unit::Stats.build(attack: 85, life: 300, shield: 100))
         | 
| 40 | 
            +
            tassadar = Unit.build(name: 'Tassadar', status: 'dead',  stats: Unit::Stats.build(attack: 0,  life: 80,  shield: 300))
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            zeratul.save
         | 
| 43 | 
            +
            tassadar.save
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            # Udate (we made error - mistakenly set Tassadar's attack as zero, let's fix it).
         | 
| 46 | 
            +
            tassadar.stats.attack = 20
         | 
| 47 | 
            +
            tassadar.save
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            # Querying first & all, there's also :each, the same as :all.
         | 
| 50 | 
            +
            Unit.first name: 'Zeratul'                         # => zeratul
         | 
| 51 | 
            +
            Unit.all name: 'Zeratul'                           # => [zeratul]
         | 
| 52 | 
            +
            Unit.all name: 'Zeratul' do |unit|
         | 
| 53 | 
            +
              unit                                             # => zeratul
         | 
| 54 | 
            +
            end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            # Simple finders (bang versions also availiable).
         | 
| 57 | 
            +
            Unit.by_name 'Zeratul'                             # => zeratul
         | 
| 58 | 
            +
            Unit.first_by_name 'Zeratul'                       # => zeratul
         | 
| 59 | 
            +
            Unit.all_by_name 'Zeratul'                         # => [zeratul]
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            # Scopes.
         | 
| 62 | 
            +
            Unit.alive.count                                   # => 1
         | 
| 63 | 
            +
            Unit.alive.first                                   # => zeratul
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            # Callbacks & callbacks on embedded models.
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            # Validations.
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            # Save model to any collection.
         | 
| 70 | 
            +
            ```
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            Source: examples/model.rb
         |