mongodb_model 0.2.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,11 +1,10 @@
1
1
  require 'rake_ext'
2
2
 
3
- project(
3
+ project \
4
4
  name: "mongodb_model",
5
- # version: '0.2.1',
5
+ version: '2.0.0',
6
6
  gem: true,
7
7
  summary: "Object Model for MongoDB",
8
8
 
9
9
  author: "Alexey Petrushin",
10
- homepage: "http://alexeypetrushin.github.com/mongodb_model"
11
- )
10
+ homepage: "http://alexeypetrushin.github.com/mongodb_model"
@@ -1,3 +1,5 @@
1
+ require 'mongo/model/support/conversions'
2
+
1
3
  module Mongo::Model::Assignment
2
4
  class Dsl < BasicObject
3
5
  def initialize model
@@ -47,9 +49,9 @@ module Mongo::Model::Assignment
47
49
  dsl = ::Mongo::Model::Assignment::Dsl.new self
48
50
  dsl.instance_eval &block
49
51
  else
50
- args.size.must_be.in 2..3
52
+ args.size.must.be_in 2..3
51
53
  attr_name = args.shift
52
- attr_name.must_be.a Symbol
54
+ attr_name.must.be_a Symbol
53
55
 
54
56
  if args.first.is_a? Class
55
57
  type, mass_assignment = args
@@ -1,8 +1,33 @@
1
1
  module Mongo::Model::Callbacks
2
2
  inherit RubyExt::Callbacks
3
3
 
4
+ protected
5
+ def with_model_callbacks methods, options, models, &block
6
+ # Firing before callbacks.
7
+ unless options[:callbacks] == false
8
+ methods.each do |method|
9
+ models.each do |model|
10
+ return false unless model.run_before_callbacks method, method
11
+ end
12
+ end
13
+ end
14
+
15
+ result = block.call
16
+
17
+ # Firing after callbacks.
18
+ unless options[:callbacks] == false
19
+ methods.reverse.each do |method|
20
+ models.each do |model|
21
+ model.run_after_callbacks method, method
22
+ end
23
+ end
24
+ end
25
+
26
+ result
27
+ end
28
+
4
29
  module ClassMethods
5
- [:validate, :create, :update, :save, :destroy].each do |method_name|
30
+ [:validate, :create, :update, :save, :delete].each do |method_name|
6
31
  define_method "before_#{method_name}" do |*args, &block|
7
32
  opt = args.extract_options!
8
33
  if block
@@ -14,7 +39,7 @@ module Mongo::Model::Callbacks
14
39
  end
15
40
  end
16
41
 
17
- [:validate, :create, :update, :save, :destroy, :build].each do |method_name|
42
+ [:validate, :create, :update, :save, :delete, :build].each do |method_name|
18
43
  define_method "after_#{method_name}" do |*args, &block|
19
44
  opt = args.extract_options!
20
45
  if block
@@ -1,10 +1,10 @@
1
1
  module Mongo::Model::Conversion
2
2
  def model_to_json *args, &block
3
- to_rson(*args, &block).to_json
3
+ to_hash(*args, &block).to_json
4
4
  end
5
5
 
6
6
  def model_to_xml *args, &block
7
- to_rson(*args, &block).to_xml
7
+ to_hash(*args, &block).to_xml
8
8
  end
9
9
 
10
10
  alias_method :to_json, :model_to_json
@@ -14,24 +14,25 @@ module Mongo::Model::Conversion
14
14
  alias_method :to_json, :model_to_json
15
15
  alias_method :to_xml, :model_to_xml
16
16
 
17
- # hack to supress ActiveSupport as_json
17
+ # Hack to supress ActiveSupport as_json.
18
18
  alias_method :as_json, :model_to_json
19
19
  alias_method :as_xml, :model_to_xml
20
20
  end
21
21
 
22
- def to_rson options = {}
23
- # hack to suppress ActiveSupport as_json
24
- options ||= {}
22
+ def to_hash options = {}
23
+ # return super if args.empty?
24
+ #
25
+ # options = args.first
25
26
 
26
27
  if profile = options[:profile]
27
28
  raise "no other optins are allowed when using :profile option!" if options.size > 1
28
29
  profile_options = self.class.profiles[profile] || raise("profile :#{profile} not defined for #{self.class}!")
29
- to_rson profile_options.merge(_profile: profile)
30
+ to_hash profile_options.merge(_profile: profile)
30
31
  else
31
32
  options.validate_options! :only, :except, :methods, :errors, :_profile
32
33
  child_options = options[:_profile] ? {profile: options[:_profile]} : {}
33
34
 
34
- instance_variables = Mongo::Object.instance_variables(self)
35
+ instance_variables = self.persistent_instance_variable_names
35
36
 
36
37
  if only = options[:only]
37
38
  instance_variables &= Array(only).collect{|n| :"@#{n}"}
@@ -42,14 +43,14 @@ module Mongo::Model::Conversion
42
43
  result = {}
43
44
  instance_variables.each do |iv_name|
44
45
  value = instance_variable_get iv_name
45
- value = Mongo::Object.convert value, :to_rson, child_options
46
+ value = convert_model value, :to_hash, child_options
46
47
  result[iv_name[1.. -1]] = value
47
48
  end
48
49
 
49
50
  methods = options[:methods] ? Array(options[:methods]) : []
50
51
  methods.each do |method|
51
52
  value = send method
52
- value = Mongo::Object.convert value, :to_rson, child_options
53
+ value = convert_model value, :to_hash, child_options
53
54
  result[method.to_s] = value
54
55
  end
55
56
 
@@ -59,11 +60,25 @@ module Mongo::Model::Conversion
59
60
  errors.each{|k, v| stringified_errors[k.to_s] = v}
60
61
  result['errors'] = stringified_errors
61
62
  end
62
-
63
+
63
64
  result
64
65
  end
65
66
  end
66
67
 
68
+ protected
69
+ def convert_model obj, method, options
70
+ if obj.respond_to? :collect_with_value
71
+ # Array or Hash.
72
+ obj.collect_with_value{|v| convert_model v, method, options}
73
+ elsif obj.respond_to? method
74
+ # Model.
75
+ obj.send method, options
76
+ else
77
+ # Simple object.
78
+ obj
79
+ end
80
+ end
81
+
67
82
  module ClassMethods
68
83
  inheritable_accessor :profiles, {}
69
84
  def profile name, options = {}
@@ -1,4 +1,29 @@
1
1
  module Mongo::Model::Crud
2
+ # Enhancing Mongo::Object CRUD.
3
+
4
+ def create_object collection, options
5
+ with_model_crud_callbacks [:save, :create], options do |mongo_options|
6
+ super collection, mongo_options
7
+ true
8
+ end
9
+ end
10
+
11
+ def update_object collection, options
12
+ with_model_crud_callbacks [:save, :update], options do |mongo_options|
13
+ super collection, mongo_options
14
+ true
15
+ end
16
+ end
17
+
18
+ def delete_object collection, options
19
+ with_model_crud_callbacks [:delete], options do |mongo_options|
20
+ super collection, mongo_options
21
+ true
22
+ end
23
+ end
24
+
25
+ # Model CRUD.
26
+
2
27
  def save options = {}
3
28
  with_collection options do |collection, options|
4
29
  collection.save self, options
@@ -9,20 +34,38 @@ module Mongo::Model::Crud
9
34
  save(*args) || raise(Mongo::Error, "can't save invalid model (#{self.errors})!")
10
35
  end
11
36
 
12
- def destroy options = {}
37
+ def delete options = {}
13
38
  with_collection options do |collection, options|
14
- collection.destroy self, options
39
+ collection.delete self, options
15
40
  end
16
41
  end
17
42
 
18
- def destroy! *args
19
- destroy(*args) || raise(Mongo::Error, "can't destroy invalid model #{self.errors}!")
43
+ def delete! *args
44
+ delete(*args) || raise(Mongo::Error, "can't delete invalid model #{self.errors}!")
20
45
  end
21
46
 
22
47
  def update doc, options = {}
23
- self.class.collection.update({_id: _id}, doc, options)
48
+ with_collection options do |collection, options|
49
+ collection.update({_id: _id}, doc, options)
50
+ end
24
51
  end
25
52
 
53
+ protected
54
+ def with_model_crud_callbacks methods, options, &block
55
+ models = [self] + embedded_models(true)
56
+
57
+ return false if (options[:validate] != false) and invalid?(options)
58
+
59
+ with_model_callbacks methods, options, models do
60
+ mongo_options = options.clone
61
+ mongo_options.delete :validate
62
+ mongo_options.delete :callbacks
63
+
64
+ block.call mongo_options
65
+ end
66
+ end
67
+
68
+
26
69
  module ClassMethods
27
70
  def build attributes = {}, options = {}, &block
28
71
  model = self.new
@@ -43,14 +86,14 @@ module Mongo::Model::Crud
43
86
  model
44
87
  end
45
88
 
46
- def destroy_all selector = {}, options = {}
89
+ def delete_all selector = {}, options = {}
47
90
  success = true
48
- each(selector){|o| success = false unless o.destroy options}
91
+ each(selector){|o| success = false unless o.delete options}
49
92
  success
50
93
  end
51
94
 
52
- def destroy_all! selector = {}, options = {}
53
- destroy_all(selector, options) || raise(Mongo::Error, "can't destroy #{selector.inspect}!")
95
+ def delete_all! selector = {}, options = {}
96
+ delete_all(selector, options) || raise(Mongo::Error, "can't delete #{selector.inspect}!")
54
97
  end
55
98
 
56
99
  def update selector, doc, options = {}
@@ -23,7 +23,7 @@ module Mongo::Model::FileModel
23
23
 
24
24
  after_save{|model| model.send(attr_name).save!}
25
25
 
26
- after_destroy{|model| model.send(attr_name).destroy!}
26
+ after_delete{|model| model.send(attr_name).delete!}
27
27
  end
28
28
  end
29
29
  end
@@ -1,4 +1,4 @@
1
- class Mongo::Model::UniquenessValidator < Validatable::ValidationBase
1
+ class Validatable::UniquenessValidator < Validatable::ValidationBase
2
2
  attr_accessor :scope, :case_sensitive
3
3
 
4
4
  def initialize(klass, attribute, options={})
@@ -18,7 +18,7 @@ class Mongo::Model::UniquenessValidator < Validatable::ValidationBase
18
18
  conditions[attribute] = /^#{Regexp.escape(value.to_s)}$/i
19
19
  end
20
20
 
21
- # Make sure we're not including the current document in the query
21
+ # Make sure we're not including the current document in the query.
22
22
  conditions[:_id] = {_ne: instance._id} if instance._id
23
23
 
24
24
  !klass.exists?(conditions)
@@ -0,0 +1,41 @@
1
+ module Validatable::Model
2
+ def errors
3
+ @_errors ||= Validatable::Errors.new
4
+ end
5
+
6
+ protected
7
+ def run_validations_for_this_model_only
8
+ self.class.validations.each do |v|
9
+ if v.respond_to?(:validate)
10
+ v.validate self
11
+ elsif v.is_a? Proc
12
+ v.call self
13
+ else
14
+ send v
15
+ end
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ include ::Validatable::Macros
21
+
22
+ inheritable_accessor :validations, []
23
+
24
+ def validates_uniqueness_of *args
25
+ add_validations(args, ::Validatable::UniquenessValidator)
26
+ end
27
+
28
+ def validate validation = nil, &block
29
+ validations.push block || validation
30
+ end
31
+
32
+ protected
33
+ def add_validations(args, klass)
34
+ options = args.last.is_a?(Hash) ? args.pop : {}
35
+ args.each do |attribute|
36
+ new_validation = klass.new self, attribute, options
37
+ validate new_validation
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,59 @@
1
+ require 'mongo/object'
2
+ require 'ruby_ext'
3
+
4
+ module Mongo::Model; end
5
+
6
+ %w(
7
+ support
8
+
9
+ db
10
+ conversion
11
+ assignment
12
+ callbacks
13
+ validation
14
+ crud
15
+ query
16
+ query_mixin
17
+ scope
18
+ attribute_convertors
19
+ misc
20
+ model
21
+ ).each{|f| require "mongo/model/#{f}"}
22
+
23
+ module Mongo
24
+ module Model
25
+ autoload :FileModel, 'mongo/model/integration/file_model'
26
+
27
+ inherit \
28
+ Db,
29
+ Conversion,
30
+ Assignment,
31
+ Callbacks,
32
+ Validation,
33
+ Crud,
34
+ QueryMixin,
35
+ Scope,
36
+ AttributeConvertors,
37
+ Misc
38
+ end
39
+ end
40
+
41
+ Mongo.defaults.merge! \
42
+ convert_underscore_to_dollar: true,
43
+ batch_size: 50,
44
+ multi: true,
45
+ safe: true,
46
+ generate_id: true
47
+
48
+ # Integration with Rails.
49
+ unless $dont_use_rails
50
+ require 'mongo/model/integration/rails' if defined? Rails
51
+ end
52
+
53
+ # Integration with Validatable2
54
+ unless $dont_use_validatable
55
+ require 'validatable'
56
+ require 'mongo/model/integration/validatable'
57
+ require 'mongo/model/integration/validatable/uniqueness_validator'
58
+ Mongo::Model.inherit Validatable::Model
59
+ end
@@ -1,12 +1,6 @@
1
1
  module Mongo::Model
2
2
  include Mongo::Object
3
3
 
4
- attr_accessor :_id, :_class
5
-
6
- def _id?; !!_id end
7
- def new?; !_id end
8
- alias_method :new_record?, :new?
9
-
10
4
  inherited do
11
5
  unless is?(Array) or is?(Hash)
12
6
  alias_method :eql?, :model_eql?
@@ -14,6 +8,8 @@ module Mongo::Model
14
8
  end
15
9
  end
16
10
 
11
+ # Equality.
12
+
17
13
  def model_eql? o
18
14
  return true if equal? o
19
15
  self.class == o.class and self == o
@@ -21,22 +17,58 @@ module Mongo::Model
21
17
 
22
18
  def model_eq? o
23
19
  return true if equal? o
20
+ return false unless o.is_a? Mongo::Model
24
21
 
25
- variables = {}; ::Mongo::Object.each_instance_variable(self){|n, v| variables[n] = v}
26
- o_variables = {}; ::Mongo::Object.each_instance_variable(o){|n, v| o_variables[n] = v}
22
+ variables = {}.tap do |h|
23
+ persistent_instance_variable_names.each{|n| h[n] = instance_variable_get(n)}
24
+ end
25
+
26
+ o_variables = {}.tap do |h|
27
+ o.persistent_instance_variable_names.each{|n| h[n] = o.instance_variable_get(n)}
28
+ end
27
29
 
28
30
  variables == o_variables
29
31
  end
30
32
 
31
- # class << self
32
- # attr_accessor :db, :connection
33
- # attr_required :db, :connection
34
- #
35
- # # Override this method to provide custom alias to db name translation,
36
- # # for example db_name = my_config[alias_name]
37
- # def resolve_db_alias alias_name
38
- # db_name = alias_name.to_s
39
- # connection.db db_name
40
- # end
41
- # end
33
+ protected
34
+ # Traversing models.
35
+
36
+ def embedded_models recursive = false
37
+ [].tap{|a| each_embedded(recursive){|m| a.add m}}
38
+ end
39
+
40
+ def each_embedded recursive = false, &block
41
+ self.class.embedded.each do |name|
42
+ if o = instance_variable_get(name)
43
+ _each_embedded o, recursive, &block
44
+ end
45
+ end
46
+ end
47
+
48
+ # Hash or Array also can be the Model by itsef, so we need to check
49
+ # for presence both `:each_embedded` and `:each_value` methods.
50
+ def _each_embedded o, recursive, &block
51
+ # If o is model adding it and its children.
52
+ if o.respond_to? :each_embedded
53
+ block.call o
54
+ o.each_embedded recursive, &block if recursive
55
+ end
56
+
57
+ # If o is Hash or Array, iterating and adding all models from there,
58
+ # recursivelly, hashes and arrays can be nested in any order.
59
+ if o.respond_to? :each_value
60
+ o.each_value do |o|
61
+ _each_embedded o, recursive, &block
62
+ end
63
+ end
64
+ end
65
+
66
+ module ClassMethods
67
+ inheritable_accessor :_embedded, []
68
+
69
+ def embedded *list
70
+ list.collect!{|n| :"@#{n}"}
71
+ if list.empty? then _embedded else _embedded.push(*list) end
72
+ end
73
+ end
42
74
  end
@@ -1,4 +1,4 @@
1
- class Mongo::Model::Query < Object
1
+ class Mongo::Model::Query
2
2
  attr_reader :model_class, :selector, :options
3
3
 
4
4
  def initialize model_class, selector = {}, options = {}
@@ -53,10 +53,8 @@ module Mongo::Model::Scope
53
53
  end
54
54
  end
55
55
 
56
+ # Finders.
56
57
 
57
- #
58
- # finders
59
- #
60
58
  def count selector = {}, options = {}
61
59
  if current = current_scope
62
60
  super current.selector.merge(selector), current.options.merge(options)
@@ -81,10 +79,8 @@ module Mongo::Model::Scope
81
79
  end
82
80
  end
83
81
 
82
+ # Shortcuts for frequently used scopes.
84
83
 
85
- #
86
- # Handy scopes
87
- #
88
84
  def limit n; query({}, limit: n) end
89
85
  def skip n; query({}, skip: n) end
90
86
  def sort *list
@@ -5,9 +5,6 @@ rspec do
5
5
  class << self
6
6
  def with_mongo_model
7
7
  with_mongo
8
- #
9
- # before{Mongo::Model.db = mongo.db}
10
- # after{Mongo::Model.db = nil}
11
8
  end
12
9
  end
13
10
  end
@@ -1,6 +1,7 @@
1
- #
2
- # Boolean
3
- #
1
+ # Converting basic Ruby types to and from String, needed
2
+ # in mass assignment.
3
+
4
+ # Boolean.
4
5
  module Mongo::Model::BooleanType
5
6
  Mapping = {
6
7
  true => true,
@@ -37,10 +38,7 @@ class Boolean; end unless defined?(Boolean)
37
38
 
38
39
  Boolean.extend Mongo::Model::BooleanType
39
40
 
40
-
41
- #
42
- # Date
43
- #
41
+ # Date.
44
42
  require 'date'
45
43
  Date.class_eval do
46
44
  def self.cast value
@@ -55,20 +53,14 @@ Date.class_eval do
55
53
  end
56
54
  end
57
55
 
58
-
59
- #
60
- # Float
61
- #
56
+ # Float.
62
57
  Float.class_eval do
63
58
  def self.cast value
64
59
  value.nil? ? nil : value.to_f
65
60
  end
66
61
  end
67
62
 
68
-
69
- #
70
- # Integer
71
- #
63
+ # Integer.
72
64
  Integer.class_eval do
73
65
  def self.cast value
74
66
  value_to_i = value.to_i
@@ -80,20 +72,14 @@ Integer.class_eval do
80
72
  end
81
73
  end
82
74
 
83
-
84
- #
85
- # String
86
- #
75
+ # String.
87
76
  String.class_eval do
88
77
  def self.cast value
89
78
  value.nil? ? nil : value.to_s
90
79
  end
91
80
  end
92
81
 
93
-
94
- #
95
- # Time
96
- #
82
+ # Time.
97
83
  Time.class_eval do
98
84
  def self.cast value
99
85
  if value.nil? || value == ''
@@ -0,0 +1,11 @@
1
+ Array.class_eval do
2
+ alias_method :each_value, :each
3
+ alias_method :collect_with_value, :collect
4
+ end
5
+
6
+ Hash.class_eval do
7
+ def collect_with_value &block
8
+ {}.tap{|h| self.each{|k, v| h[k] = block.call v}}
9
+ end
10
+ end
11
+
@@ -1,41 +1,21 @@
1
1
  module Mongo::Model::Validation
2
- def errors
3
- @_errors ||= Validatable::Errors.new
2
+ def valid? options = {}
3
+ errors.clear
4
+ run_validations options
4
5
  end
6
+ def invalid?(options = {}); !valid?(options) end
5
7
 
6
- def run_model_validations
7
- self.class.validations.each do |v|
8
- if v.respond_to?(:validate)
9
- v.validate self
10
- elsif v.is_a? Proc
11
- v.call self
12
- else
13
- send v
14
- end
15
- end
16
- true
17
- end
8
+ protected
9
+ def run_validations options = {}
10
+ with_model_callbacks [:validate], options, [self] do
11
+ # Validating main model.
12
+ self.run_validations_for_this_model_only
13
+ result = errors.empty?
18
14
 
19
- module ClassMethods
20
- include ::Validatable::Macros
21
-
22
- inheritable_accessor :validations, []
23
-
24
- def validates_uniqueness_of *args
25
- add_validations(args, Mongo::Model::UniquenessValidator)
26
- end
27
-
28
- def validate validation = nil, &block
29
- validations.push block || validation
30
- end
31
-
32
- protected
33
- def add_validations(args, klass)
34
- options = args.last.is_a?(Hash) ? args.pop : {}
35
- args.each do |attribute|
36
- new_validation = klass.new self, attribute, options
37
- validate new_validation
15
+ # Validating embedded models.
16
+ embedded_models.reduce result do |result, model|
17
+ result &= model.valid?(options)
38
18
  end
39
19
  end
40
- end
20
+ end
41
21
  end