mark_mapper 0.0.1

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.
Files changed (211) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.rdoc +39 -0
  4. data/examples/attr_accessible.rb +24 -0
  5. data/examples/attr_protected.rb +24 -0
  6. data/examples/cache_key.rb +26 -0
  7. data/examples/custom_types.rb +26 -0
  8. data/examples/identity_map.rb +30 -0
  9. data/examples/identity_map/automatic.rb +2 -0
  10. data/examples/keys.rb +42 -0
  11. data/examples/modifiers/set.rb +27 -0
  12. data/examples/plugins.rb +40 -0
  13. data/examples/querying.rb +39 -0
  14. data/examples/sample_app.rb +43 -0
  15. data/examples/scopes.rb +56 -0
  16. data/examples/validating/embedded_docs.rb +31 -0
  17. data/lib/mark_mapper.rb +125 -0
  18. data/lib/mark_mapper/config.rb +90 -0
  19. data/lib/mark_mapper/connection.rb +60 -0
  20. data/lib/mark_mapper/criteria_hash.rb +194 -0
  21. data/lib/mark_mapper/document.rb +46 -0
  22. data/lib/mark_mapper/embedded_document.rb +32 -0
  23. data/lib/mark_mapper/exceptions.rb +33 -0
  24. data/lib/mark_mapper/extensions/array.rb +27 -0
  25. data/lib/mark_mapper/extensions/boolean.rb +45 -0
  26. data/lib/mark_mapper/extensions/date.rb +29 -0
  27. data/lib/mark_mapper/extensions/duplicable.rb +86 -0
  28. data/lib/mark_mapper/extensions/float.rb +18 -0
  29. data/lib/mark_mapper/extensions/hash.rb +26 -0
  30. data/lib/mark_mapper/extensions/integer.rb +27 -0
  31. data/lib/mark_mapper/extensions/kernel.rb +11 -0
  32. data/lib/mark_mapper/extensions/nil_class.rb +18 -0
  33. data/lib/mark_mapper/extensions/object.rb +30 -0
  34. data/lib/mark_mapper/extensions/object_id.rb +18 -0
  35. data/lib/mark_mapper/extensions/set.rb +20 -0
  36. data/lib/mark_mapper/extensions/string.rb +31 -0
  37. data/lib/mark_mapper/extensions/symbol.rb +87 -0
  38. data/lib/mark_mapper/extensions/time.rb +29 -0
  39. data/lib/mark_mapper/locale/en.yml +5 -0
  40. data/lib/mark_mapper/middleware/identity_map.rb +41 -0
  41. data/lib/mark_mapper/normalizers/criteria_hash_key.rb +17 -0
  42. data/lib/mark_mapper/normalizers/criteria_hash_value.rb +66 -0
  43. data/lib/mark_mapper/normalizers/fields_value.rb +26 -0
  44. data/lib/mark_mapper/normalizers/hash_key.rb +19 -0
  45. data/lib/mark_mapper/normalizers/integer.rb +19 -0
  46. data/lib/mark_mapper/normalizers/options_hash_value.rb +83 -0
  47. data/lib/mark_mapper/normalizers/sort_value.rb +55 -0
  48. data/lib/mark_mapper/options_hash.rb +103 -0
  49. data/lib/mark_mapper/pagination.rb +6 -0
  50. data/lib/mark_mapper/pagination/collection.rb +32 -0
  51. data/lib/mark_mapper/pagination/paginator.rb +46 -0
  52. data/lib/mark_mapper/plugins.rb +22 -0
  53. data/lib/mark_mapper/plugins/accessible.rb +61 -0
  54. data/lib/mark_mapper/plugins/active_model.rb +18 -0
  55. data/lib/mark_mapper/plugins/associations.rb +96 -0
  56. data/lib/mark_mapper/plugins/associations/base.rb +98 -0
  57. data/lib/mark_mapper/plugins/associations/belongs_to_association.rb +63 -0
  58. data/lib/mark_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +35 -0
  59. data/lib/mark_mapper/plugins/associations/belongs_to_proxy.rb +52 -0
  60. data/lib/mark_mapper/plugins/associations/collection.rb +29 -0
  61. data/lib/mark_mapper/plugins/associations/embedded_collection.rb +44 -0
  62. data/lib/mark_mapper/plugins/associations/in_array_proxy.rb +133 -0
  63. data/lib/mark_mapper/plugins/associations/many_association.rb +63 -0
  64. data/lib/mark_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  65. data/lib/mark_mapper/plugins/associations/many_documents_proxy.rb +142 -0
  66. data/lib/mark_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
  67. data/lib/mark_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
  68. data/lib/mark_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
  69. data/lib/mark_mapper/plugins/associations/one_as_proxy.rb +22 -0
  70. data/lib/mark_mapper/plugins/associations/one_association.rb +48 -0
  71. data/lib/mark_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +30 -0
  72. data/lib/mark_mapper/plugins/associations/one_embedded_proxy.rb +44 -0
  73. data/lib/mark_mapper/plugins/associations/one_proxy.rb +95 -0
  74. data/lib/mark_mapper/plugins/associations/proxy.rb +138 -0
  75. data/lib/mark_mapper/plugins/associations/single_association.rb +46 -0
  76. data/lib/mark_mapper/plugins/caching.rb +21 -0
  77. data/lib/mark_mapper/plugins/callbacks.rb +42 -0
  78. data/lib/mark_mapper/plugins/clone.rb +24 -0
  79. data/lib/mark_mapper/plugins/counter_cache.rb +97 -0
  80. data/lib/mark_mapper/plugins/dirty.rb +61 -0
  81. data/lib/mark_mapper/plugins/document.rb +41 -0
  82. data/lib/mark_mapper/plugins/dumpable.rb +22 -0
  83. data/lib/mark_mapper/plugins/dynamic_querying.rb +45 -0
  84. data/lib/mark_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
  85. data/lib/mark_mapper/plugins/embedded_callbacks.rb +81 -0
  86. data/lib/mark_mapper/plugins/embedded_document.rb +53 -0
  87. data/lib/mark_mapper/plugins/equality.rb +23 -0
  88. data/lib/mark_mapper/plugins/identity_map.rb +144 -0
  89. data/lib/mark_mapper/plugins/indexable.rb +86 -0
  90. data/lib/mark_mapper/plugins/inspect.rb +16 -0
  91. data/lib/mark_mapper/plugins/keys.rb +470 -0
  92. data/lib/mark_mapper/plugins/keys/key.rb +134 -0
  93. data/lib/mark_mapper/plugins/keys/static.rb +45 -0
  94. data/lib/mark_mapper/plugins/logger.rb +18 -0
  95. data/lib/mark_mapper/plugins/modifiers.rb +140 -0
  96. data/lib/mark_mapper/plugins/pagination.rb +16 -0
  97. data/lib/mark_mapper/plugins/partial_updates.rb +77 -0
  98. data/lib/mark_mapper/plugins/persistence.rb +79 -0
  99. data/lib/mark_mapper/plugins/protected.rb +45 -0
  100. data/lib/mark_mapper/plugins/querying.rb +173 -0
  101. data/lib/mark_mapper/plugins/querying/decorated_markmapper_query.rb +75 -0
  102. data/lib/mark_mapper/plugins/rails.rb +79 -0
  103. data/lib/mark_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
  104. data/lib/mark_mapper/plugins/sci.rb +82 -0
  105. data/lib/mark_mapper/plugins/scopes.rb +28 -0
  106. data/lib/mark_mapper/plugins/serialization.rb +109 -0
  107. data/lib/mark_mapper/plugins/timestamps.rb +29 -0
  108. data/lib/mark_mapper/plugins/touch.rb +18 -0
  109. data/lib/mark_mapper/plugins/userstamps.rb +18 -0
  110. data/lib/mark_mapper/plugins/validations.rb +96 -0
  111. data/lib/mark_mapper/query.rb +278 -0
  112. data/lib/mark_mapper/railtie.rb +52 -0
  113. data/lib/mark_mapper/railtie/database.rake +65 -0
  114. data/lib/mark_mapper/translation.rb +10 -0
  115. data/lib/mark_mapper/version.rb +4 -0
  116. data/lib/rails/generators/mark_mapper/config/config_generator.rb +37 -0
  117. data/lib/rails/generators/mark_mapper/config/templates/marklogic.yml +19 -0
  118. data/lib/rails/generators/mark_mapper/model/model_generator.rb +40 -0
  119. data/lib/rails/generators/mark_mapper/model/templates/model.rb +17 -0
  120. data/spec/config/mark_mapper.yml +6 -0
  121. data/spec/examples_spec.rb +25 -0
  122. data/spec/functional/accessible_spec.rb +198 -0
  123. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +64 -0
  124. data/spec/functional/associations/belongs_to_proxy_spec.rb +255 -0
  125. data/spec/functional/associations/in_array_proxy_spec.rb +349 -0
  126. data/spec/functional/associations/many_documents_as_proxy_spec.rb +230 -0
  127. data/spec/functional/associations/many_documents_proxy_spec.rb +968 -0
  128. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +238 -0
  129. data/spec/functional/associations/many_embedded_proxy_spec.rb +288 -0
  130. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +302 -0
  131. data/spec/functional/associations/one_as_proxy_spec.rb +489 -0
  132. data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +207 -0
  133. data/spec/functional/associations/one_embedded_proxy_spec.rb +100 -0
  134. data/spec/functional/associations/one_proxy_spec.rb +406 -0
  135. data/spec/functional/associations_spec.rb +48 -0
  136. data/spec/functional/caching_spec.rb +75 -0
  137. data/spec/functional/callbacks_spec.rb +330 -0
  138. data/spec/functional/counter_cache_spec.rb +235 -0
  139. data/spec/functional/dirty_spec.rb +316 -0
  140. data/spec/functional/document_spec.rb +310 -0
  141. data/spec/functional/dumpable_spec.rb +24 -0
  142. data/spec/functional/dynamic_querying_spec.rb +75 -0
  143. data/spec/functional/embedded_document_spec.rb +316 -0
  144. data/spec/functional/equality_spec.rb +20 -0
  145. data/spec/functional/extensions_spec.rb +16 -0
  146. data/spec/functional/identity_map_spec.rb +483 -0
  147. data/spec/functional/keys_spec.rb +339 -0
  148. data/spec/functional/logger_spec.rb +20 -0
  149. data/spec/functional/modifiers_spec.rb +446 -0
  150. data/spec/functional/options_hash_spec.rb +41 -0
  151. data/spec/functional/pagination_spec.rb +89 -0
  152. data/spec/functional/partial_updates_spec.rb +530 -0
  153. data/spec/functional/protected_spec.rb +199 -0
  154. data/spec/functional/querying_spec.rb +984 -0
  155. data/spec/functional/rails_spec.rb +55 -0
  156. data/spec/functional/sci_spec.rb +374 -0
  157. data/spec/functional/scopes_spec.rb +204 -0
  158. data/spec/functional/static_keys_spec.rb +153 -0
  159. data/spec/functional/timestamps_spec.rb +97 -0
  160. data/spec/functional/touch_spec.rb +125 -0
  161. data/spec/functional/userstamps_spec.rb +46 -0
  162. data/spec/functional/validations_spec.rb +416 -0
  163. data/spec/quality_spec.rb +51 -0
  164. data/spec/spec_helper.rb +150 -0
  165. data/spec/support/matchers.rb +15 -0
  166. data/spec/support/models.rb +256 -0
  167. data/spec/symbol_operator_spec.rb +70 -0
  168. data/spec/symbol_spec.rb +9 -0
  169. data/spec/unit/associations/base_spec.rb +146 -0
  170. data/spec/unit/associations/belongs_to_association_spec.rb +30 -0
  171. data/spec/unit/associations/many_association_spec.rb +64 -0
  172. data/spec/unit/associations/one_association_spec.rb +48 -0
  173. data/spec/unit/associations/proxy_spec.rb +103 -0
  174. data/spec/unit/clone_spec.rb +79 -0
  175. data/spec/unit/config_generator_spec.rb +24 -0
  176. data/spec/unit/criteria_hash_spec.rb +218 -0
  177. data/spec/unit/document_spec.rb +251 -0
  178. data/spec/unit/dynamic_finder_spec.rb +125 -0
  179. data/spec/unit/embedded_document_spec.rb +676 -0
  180. data/spec/unit/equality_spec.rb +38 -0
  181. data/spec/unit/exceptions_spec.rb +12 -0
  182. data/spec/unit/extensions_spec.rb +368 -0
  183. data/spec/unit/identity_map_middleware_spec.rb +134 -0
  184. data/spec/unit/inspect_spec.rb +47 -0
  185. data/spec/unit/key_spec.rb +276 -0
  186. data/spec/unit/keys_spec.rb +155 -0
  187. data/spec/unit/mark_mapper_spec.rb +37 -0
  188. data/spec/unit/model_generator_spec.rb +45 -0
  189. data/spec/unit/normalizers/criteria_hash_key_spec.rb +37 -0
  190. data/spec/unit/normalizers/criteria_hash_value_spec.rb +200 -0
  191. data/spec/unit/normalizers/fields_value_spec.rb +45 -0
  192. data/spec/unit/normalizers/hash_key_spec.rb +15 -0
  193. data/spec/unit/normalizers/integer_spec.rb +24 -0
  194. data/spec/unit/normalizers/options_hash_value_spec.rb +99 -0
  195. data/spec/unit/normalizers/sort_value_spec.rb +98 -0
  196. data/spec/unit/options_hash_spec.rb +64 -0
  197. data/spec/unit/pagination/collection_spec.rb +30 -0
  198. data/spec/unit/pagination/paginator_spec.rb +118 -0
  199. data/spec/unit/pagination_spec.rb +11 -0
  200. data/spec/unit/plugins_spec.rb +89 -0
  201. data/spec/unit/query_spec.rb +837 -0
  202. data/spec/unit/rails_compatibility_spec.rb +40 -0
  203. data/spec/unit/rails_reflect_on_association_spec.rb +118 -0
  204. data/spec/unit/rails_spec.rb +188 -0
  205. data/spec/unit/serialization_spec.rb +169 -0
  206. data/spec/unit/serializers/json_serializer_spec.rb +218 -0
  207. data/spec/unit/serializers/xml_serializer_spec.rb +198 -0
  208. data/spec/unit/time_zones_spec.rb +44 -0
  209. data/spec/unit/translation_spec.rb +27 -0
  210. data/spec/unit/validations_spec.rb +588 -0
  211. metadata +307 -0
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ module MarkMapper
3
+ module Plugins
4
+ module Scopes
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :_scopes
9
+ end
10
+
11
+ module ClassMethods
12
+ def scope(name, scope_options={})
13
+ # Assign to _scopes instead of using []= to avoid mixing subclass scopes
14
+ self._scopes = scopes.merge(name => lambda do |*args|
15
+ result = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
16
+ result = self.query(result) if result.is_a?(Hash)
17
+ self.query.merge(result)
18
+ end)
19
+ singleton_class.send :define_method, name, &scopes[name]
20
+ end
21
+
22
+ def scopes
23
+ self._scopes ||= {}
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: UTF-8
2
+ require 'active_model/serializers/json'
3
+ require 'active_model/serializers/xml'
4
+
5
+ module MarkMapper
6
+ module Plugins
7
+ module Serialization
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ include ::ActiveModel::Serializers::JSON
12
+ include ::ActiveModel::Serializers::Xml
13
+ self.include_root_in_json = false
14
+ end
15
+
16
+ def serializable_attributes
17
+ attributes.keys.map { |k| k.to_s } + ['id'] - ['_id']
18
+ end
19
+
20
+ def serializable_hash(options = nil)
21
+ options ||= {}
22
+
23
+ options[:only] = Array.wrap(options[:only]).map { |k| k.to_s }
24
+ options[:except] = Array.wrap(options[:except]).map { |k| k.to_s }
25
+
26
+ attribute_names = serializable_attributes
27
+
28
+ if options[:only].any?
29
+ attribute_names &= options[:only]
30
+ elsif options[:except].any?
31
+ attribute_names -= options[:except]
32
+ end
33
+
34
+ attribute_names += Array.wrap(options[:methods]).map { |m| m.to_s }.select do |method|
35
+ respond_to?(method)
36
+ end
37
+
38
+ hash = attribute_names.sort.inject({}) do |hash, name|
39
+ value = send(name)
40
+ hash[name] = if value.is_a?(Array)
41
+ value.map {|v| v.respond_to?(:serializable_hash) ? v.serializable_hash : v }
42
+ elsif value.respond_to?(:serializable_hash)
43
+ value.serializable_hash
44
+ else
45
+ value
46
+ end
47
+ hash
48
+ end
49
+
50
+ serializable_add_includes(options) do |association, records, opts|
51
+ hash[association.to_s] = records.is_a?(Array) ?
52
+ records.map { |r| r.serializable_hash(opts) } :
53
+ records.serializable_hash(opts)
54
+ end
55
+
56
+ hash
57
+ end
58
+
59
+ def to_xml(options = {}, &block)
60
+ XmlSerializer.new(self, options).serialize(&block)
61
+ end
62
+
63
+ private
64
+
65
+ def serializable_add_includes(options = {})
66
+ return unless include_associations = options.delete(:include)
67
+
68
+ base_only_or_except = { :except => options[:except],
69
+ :only => options[:only] }
70
+
71
+ include_has_options = include_associations.is_a?(Hash)
72
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
73
+
74
+ associations.each do |association|
75
+ records = get_proxy(self.class.associations[association])
76
+ unless records.nil?
77
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
78
+ opts = options.merge(association_options)
79
+ yield(association, records, opts)
80
+ end
81
+ end
82
+
83
+ options[:include] = include_associations
84
+ end
85
+
86
+ module ClassMethods
87
+ def from_json(json)
88
+ self.new.from_json(json)
89
+ end
90
+
91
+ def from_xml(xml)
92
+ self.new.from_xml(xml)
93
+ end
94
+ end
95
+ end
96
+
97
+ # Override default Serializer to use #serializable_hash
98
+ class XmlSerializer < ::ActiveModel::Serializers::Xml::Serializer
99
+ def attributes_hash
100
+ @serializable.serializable_hash(options)
101
+ end
102
+
103
+ def serializable_methods
104
+ # Methods are already included in #serializable_hash
105
+ []
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ module MarkMapper
3
+ module Plugins
4
+ module Timestamps
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :record_timestamps
9
+ self.record_timestamps = true
10
+ end
11
+
12
+ module ClassMethods
13
+ def timestamps!
14
+ key :created_at, Time
15
+ key :updated_at, Time
16
+ class_eval { before_save :update_timestamps }
17
+ end
18
+ end
19
+
20
+ def update_timestamps
21
+ if self.record_timestamps
22
+ now = Time.now.utc
23
+ self[:created_at] = now if !persisted? && !created_at?
24
+ self[:updated_at] = now
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ module MarkMapper
2
+ module Plugins
3
+ module Touch
4
+ extend ActiveSupport::Concern
5
+
6
+ def touch(key = :updated_at)
7
+ raise ArgumentError, "Invalid key named #{key}" unless self.key_names.include?(key.to_s)
8
+ if self.class.embeddable?
9
+ self.write_attribute(key, Time.now.utc)
10
+ self._parent_document.touch
11
+ else
12
+ self.set(key => Time.now.utc)
13
+ end
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+ module MarkMapper
3
+ module Plugins
4
+ module Userstamps
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def userstamps!(options = {})
9
+ class_name = (options[:class_name] || options[:class] || 'User' ).to_s
10
+ key :creator_id, ObjectId
11
+ key :updater_id, ObjectId
12
+ belongs_to :creator, :class_name => class_name
13
+ belongs_to :updater, :class_name => class_name
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: UTF-8
2
+ module MarkMapper
3
+ module Plugins
4
+ module Validations
5
+ extend ActiveSupport::Concern
6
+
7
+ include ::ActiveModel::Validations
8
+ include ::ActiveModel::Validations::Callbacks
9
+
10
+ module ClassMethods
11
+ def validates_uniqueness_of(*attr_names)
12
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
13
+ end
14
+
15
+ def validates_associated(*attr_names)
16
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
17
+ end
18
+ end
19
+
20
+ def save(options = {})
21
+ options = options.reverse_merge(:validate => true)
22
+ !options[:validate] || valid? ? super : false
23
+ end
24
+
25
+ def valid?(context = nil)
26
+ context ||= (new_record? ? :create : :update)
27
+ super(context)
28
+ end
29
+
30
+ class UniquenessValidator < ::ActiveModel::EachValidator
31
+ def initialize(options)
32
+ @klass = options[:class] #only provided in ActiveModel 4.1 and higher
33
+ super(options.reverse_merge(:case_sensitive => true))
34
+ end
35
+
36
+ if ::ActiveModel::VERSION::MAJOR < 4 ||
37
+ (::ActiveModel::VERSION::MAJOR == 4 && ::ActiveModel::VERSION::MINOR == 0)
38
+
39
+ def setup(klass)
40
+ @klass = klass
41
+ end
42
+ end
43
+
44
+ def validate_each(record, attribute, value)
45
+ conditions = scope_conditions(record)
46
+
47
+ if options[:case_sensitive] == true
48
+ conditions[attribute] = value
49
+ else
50
+ conditions[attribute] = {
51
+ '$eq' => value.to_s.downcase,
52
+ options: {
53
+ case_sensitive: false
54
+ }
55
+ }
56
+ end
57
+
58
+ # Make sure we're not including the current document in the query
59
+ conditions[:_id.ne] = record._id if record._id
60
+
61
+ if @klass.exists?(conditions)
62
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
63
+ end
64
+ end
65
+
66
+ def message(instance)
67
+ super || "has already been taken"
68
+ end
69
+
70
+ def scope_conditions(instance)
71
+ Array(options[:scope] || []).inject({}) do |conditions, key|
72
+ conditions.merge(key => instance[key])
73
+ end
74
+ end
75
+ end
76
+
77
+ class AssociatedValidator < ::ActiveModel::EachValidator
78
+ def validate_each(record, attribute, value)
79
+ if !Array.wrap(value).all? { |c| c.nil? || c.valid?(options[:context]) }
80
+ record.errors.add(attribute, :invalid, :message => options[:message], :value => value)
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+
89
+ # Need to monkey patch ActiveModel for now since it uses the internal
90
+ # _run_validation_callbacks, which is impossible to override due to the
91
+ # way ActiveSupport::Callbacks is implemented.
92
+ ActiveModel::Validations::Callbacks.class_eval do
93
+ def run_validations!
94
+ run_callbacks(:validation) { super }
95
+ end
96
+ end
@@ -0,0 +1,278 @@
1
+ # encoding: UTF-8
2
+ require 'set'
3
+ require 'forwardable'
4
+
5
+ module MarkMapper
6
+ class Query
7
+ include Enumerable
8
+ extend Forwardable
9
+
10
+ # Private
11
+ OptionKeys = Set[
12
+ :select, :offset, :order, # MM
13
+ :fields, :skip, :limit, :sort, # :hint, :snapshot, # Ruby Driver
14
+ :debug, :return_results, :return_query, # Ruby Driver
15
+ :format, :page, :per_page, :view, # Ruby Driver
16
+ :transformer # Ruby Driver
17
+ ]
18
+
19
+ attr_reader :criteria, :options, :collection
20
+
21
+ def_delegator :@criteria, :simple?
22
+ def_delegator :@options, :fields?
23
+ # def_delegators :to_a, :include?
24
+
25
+ # Public
26
+ def initialize(collection, query_options = {})
27
+ @collection, @options, @criteria = collection, OptionsHash.new, CriteriaHash.new
28
+ query_options.each { |key, value| self[key] = value }
29
+ end
30
+
31
+ def initialize_copy(original)
32
+ super
33
+ @criteria = @criteria.dup
34
+ @options = @options.dup
35
+ end
36
+
37
+ # Public
38
+ def object_ids(*keys)
39
+ return @criteria.object_ids if keys.empty?
40
+ @criteria.object_ids = *keys
41
+ self
42
+ end
43
+
44
+ # finder DSL methods to delegate to your model if you're building an ODM
45
+ # e.g. MyModel.last needs to be equivalent to MyModel.query.last
46
+ module DSL
47
+ def per_page(limit=nil)
48
+ return @per_page || MarkLogic::Cursor::DEFAULT_PAGE_LENGTH if limit.nil?
49
+ @per_page = limit
50
+ self
51
+ end
52
+
53
+ def paginate(opts={})
54
+ page = opts.delete(:page)
55
+ limit = opts.delete(:per_page) || per_page
56
+ total_entries = opts.delete(:total_entries)
57
+ query = clone.amend(opts)
58
+ paginator = Pagination::Paginator.new(total_entries || query.count, page, limit)
59
+ docs = query.amend({
60
+ :limit => paginator.limit,
61
+ :skip => paginator.skip,
62
+ }).all
63
+
64
+ Pagination::Collection.new(docs, paginator)
65
+ end
66
+
67
+ def find_each(opts={})
68
+ query = clone.amend(opts)
69
+
70
+ if block_given?
71
+ result = nil
72
+ query.cursor do |cursor|
73
+ result = cursor
74
+ cursor.each { |doc| yield doc }
75
+ cursor.rewind!
76
+ end
77
+ result
78
+ else
79
+ query.cursor
80
+ end
81
+ end
82
+
83
+ def find_one(opts={})
84
+ query = clone.amend(opts)
85
+ query.collection.find_one(query.criteria_hash, query.options_hash)
86
+ end
87
+
88
+ def find(*ids)
89
+ return nil if ids.empty?
90
+
91
+ single_id_find = ids.size == 1 && !ids[0].is_a?(Array)
92
+
93
+ if single_id_find
94
+ first(:_id => ids[0])
95
+ else
96
+ all(:_id => ids.flatten)
97
+ end
98
+ end
99
+
100
+ def all(opts={})
101
+ [].tap do |docs|
102
+ find_each(opts) {|doc| docs << doc }
103
+ end
104
+ end
105
+
106
+ def last(opts={})
107
+ clone.amend(opts).reverse.find_one
108
+ end
109
+
110
+ def remove(opts={})
111
+ query = clone.amend(opts)
112
+ query.collection.remove(query.criteria_hash, query.options_hash)
113
+ end
114
+
115
+ def count(opts={})
116
+ query = clone.amend(opts)
117
+ cursor = query.cursor
118
+ cursor.count
119
+ end
120
+
121
+ # def distinct(key, opts = {})
122
+ # query = clone.amend(opts)
123
+ # query.collection.distinct(key, query.criteria_hash)
124
+ # end
125
+
126
+ def fields(*args)
127
+ clone.tap { |query| query.options[:fields] = *args }
128
+ end
129
+
130
+ def ignore(*args)
131
+ set_field_inclusion(args, 0)
132
+ end
133
+
134
+ def only(*args)
135
+ set_field_inclusion(args, 1)
136
+ end
137
+
138
+ def limit(count=nil)
139
+ clone.tap { |query| query.options[:limit] = count }
140
+ end
141
+
142
+ def reverse
143
+ clone.tap do |query|
144
+ sort = query[:sort]
145
+ if sort.nil?
146
+ query.options[:sort] = [[:_id, -1]]
147
+ else
148
+ query.options[:sort] = sort.map { |s| [s[0], -s[1]] }
149
+ end
150
+ end
151
+ end
152
+
153
+ def skip(count=nil)
154
+ clone.tap { |query| query.options[:skip] = count }
155
+ end
156
+
157
+ def sort(*args)
158
+ clone.tap { |query| query.options[:sort] = *args }
159
+ end
160
+
161
+ def where(hash={})
162
+ clone.tap { |query| query.criteria.merge!(CriteriaHash.new(hash)) }
163
+ end
164
+
165
+ def empty?
166
+ count == 0
167
+ end
168
+
169
+ def exists?(query_options={})
170
+ !only(:_id).find_one(query_options).nil?
171
+ end
172
+
173
+ alias_method :each, :find_each
174
+ alias_method :first, :find_one
175
+ alias_method :size, :count
176
+ alias_method :offset, :skip
177
+ alias_method :order, :sort
178
+ alias_method :exist?, :exists?
179
+ alias_method :filter, :where
180
+ alias_method :to_a, :all
181
+ end
182
+ include DSL
183
+
184
+ # def update(document, driver_opts={})
185
+ # query = clone
186
+ # query.collection.update(query.criteria_hash, document, driver_opts)
187
+ # end
188
+
189
+ def amend(opts={})
190
+ opts.each { |key, value| self[key] = value }
191
+ self
192
+ end
193
+
194
+ def [](key)
195
+ key = symbolized_key(key)
196
+ source = hash_for_key(key)
197
+ source[key]
198
+ end
199
+
200
+ def []=(key, value)
201
+ key = symbolized_key(key)
202
+ source = hash_for_key(key)
203
+ source[key] = value
204
+ end
205
+
206
+ def merge(other)
207
+ merged_criteria = @criteria.merge(other.criteria).to_hash
208
+ merged_options = @options.merge(other.options).to_hash
209
+ clone.amend(merged_criteria).amend(merged_options)
210
+ end
211
+
212
+ def to_hash
213
+ criteria_hash.merge(options_hash)
214
+ end
215
+
216
+ def explain
217
+ @collection.find(query.criteria_hash, query.options_hash).explain
218
+ end
219
+
220
+ def inspect
221
+ as_nice_string = to_hash.collect do |key, value|
222
+ " #{key}: #{value.inspect}"
223
+ end.sort.join(",")
224
+ "#<#{self.class}#{as_nice_string}>"
225
+ end
226
+
227
+ def criteria_hash
228
+ @criteria.to_hash
229
+ end
230
+
231
+ def options_hash
232
+ @options.to_hash
233
+ end
234
+
235
+ def cursor(&block)
236
+ @collection.find(criteria_hash, options_hash, &block)
237
+ end
238
+
239
+ # def to_cursor_options
240
+ # options_hash.merge({
241
+ # :query => qb.from_criteria(criteria_hash.to_hash)
242
+ # })
243
+ # end
244
+
245
+ private
246
+
247
+ # Private
248
+ def qb
249
+ @qb || MarkLogic::QueryBuilder.new(@collection)
250
+ end
251
+
252
+ # Private
253
+ def hash_for_key(key)
254
+ options_key?(key) ? @options : @criteria
255
+ end
256
+
257
+ # Private
258
+ def symbolized_key(key)
259
+ if key.respond_to?(:to_sym)
260
+ key.to_sym
261
+ else
262
+ key
263
+ end
264
+ end
265
+
266
+ # Private
267
+ def options_key?(key)
268
+ OptionKeys.include?(key)
269
+ end
270
+
271
+ # Private
272
+ def set_field_inclusion(fields, value)
273
+ fields_option = {}
274
+ fields.each { |field| fields_option[symbolized_key(field)] = value }
275
+ clone.tap { |query| query.options[:fields] = fields_option }
276
+ end
277
+ end
278
+ end