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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.rdoc +39 -0
- data/examples/attr_accessible.rb +24 -0
- data/examples/attr_protected.rb +24 -0
- data/examples/cache_key.rb +26 -0
- data/examples/custom_types.rb +26 -0
- data/examples/identity_map.rb +30 -0
- data/examples/identity_map/automatic.rb +2 -0
- data/examples/keys.rb +42 -0
- data/examples/modifiers/set.rb +27 -0
- data/examples/plugins.rb +40 -0
- data/examples/querying.rb +39 -0
- data/examples/sample_app.rb +43 -0
- data/examples/scopes.rb +56 -0
- data/examples/validating/embedded_docs.rb +31 -0
- data/lib/mark_mapper.rb +125 -0
- data/lib/mark_mapper/config.rb +90 -0
- data/lib/mark_mapper/connection.rb +60 -0
- data/lib/mark_mapper/criteria_hash.rb +194 -0
- data/lib/mark_mapper/document.rb +46 -0
- data/lib/mark_mapper/embedded_document.rb +32 -0
- data/lib/mark_mapper/exceptions.rb +33 -0
- data/lib/mark_mapper/extensions/array.rb +27 -0
- data/lib/mark_mapper/extensions/boolean.rb +45 -0
- data/lib/mark_mapper/extensions/date.rb +29 -0
- data/lib/mark_mapper/extensions/duplicable.rb +86 -0
- data/lib/mark_mapper/extensions/float.rb +18 -0
- data/lib/mark_mapper/extensions/hash.rb +26 -0
- data/lib/mark_mapper/extensions/integer.rb +27 -0
- data/lib/mark_mapper/extensions/kernel.rb +11 -0
- data/lib/mark_mapper/extensions/nil_class.rb +18 -0
- data/lib/mark_mapper/extensions/object.rb +30 -0
- data/lib/mark_mapper/extensions/object_id.rb +18 -0
- data/lib/mark_mapper/extensions/set.rb +20 -0
- data/lib/mark_mapper/extensions/string.rb +31 -0
- data/lib/mark_mapper/extensions/symbol.rb +87 -0
- data/lib/mark_mapper/extensions/time.rb +29 -0
- data/lib/mark_mapper/locale/en.yml +5 -0
- data/lib/mark_mapper/middleware/identity_map.rb +41 -0
- data/lib/mark_mapper/normalizers/criteria_hash_key.rb +17 -0
- data/lib/mark_mapper/normalizers/criteria_hash_value.rb +66 -0
- data/lib/mark_mapper/normalizers/fields_value.rb +26 -0
- data/lib/mark_mapper/normalizers/hash_key.rb +19 -0
- data/lib/mark_mapper/normalizers/integer.rb +19 -0
- data/lib/mark_mapper/normalizers/options_hash_value.rb +83 -0
- data/lib/mark_mapper/normalizers/sort_value.rb +55 -0
- data/lib/mark_mapper/options_hash.rb +103 -0
- data/lib/mark_mapper/pagination.rb +6 -0
- data/lib/mark_mapper/pagination/collection.rb +32 -0
- data/lib/mark_mapper/pagination/paginator.rb +46 -0
- data/lib/mark_mapper/plugins.rb +22 -0
- data/lib/mark_mapper/plugins/accessible.rb +61 -0
- data/lib/mark_mapper/plugins/active_model.rb +18 -0
- data/lib/mark_mapper/plugins/associations.rb +96 -0
- data/lib/mark_mapper/plugins/associations/base.rb +98 -0
- data/lib/mark_mapper/plugins/associations/belongs_to_association.rb +63 -0
- data/lib/mark_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +35 -0
- data/lib/mark_mapper/plugins/associations/belongs_to_proxy.rb +52 -0
- data/lib/mark_mapper/plugins/associations/collection.rb +29 -0
- data/lib/mark_mapper/plugins/associations/embedded_collection.rb +44 -0
- data/lib/mark_mapper/plugins/associations/in_array_proxy.rb +133 -0
- data/lib/mark_mapper/plugins/associations/many_association.rb +63 -0
- data/lib/mark_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
- data/lib/mark_mapper/plugins/associations/many_documents_proxy.rb +142 -0
- data/lib/mark_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
- data/lib/mark_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
- data/lib/mark_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
- data/lib/mark_mapper/plugins/associations/one_as_proxy.rb +22 -0
- data/lib/mark_mapper/plugins/associations/one_association.rb +48 -0
- data/lib/mark_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +30 -0
- data/lib/mark_mapper/plugins/associations/one_embedded_proxy.rb +44 -0
- data/lib/mark_mapper/plugins/associations/one_proxy.rb +95 -0
- data/lib/mark_mapper/plugins/associations/proxy.rb +138 -0
- data/lib/mark_mapper/plugins/associations/single_association.rb +46 -0
- data/lib/mark_mapper/plugins/caching.rb +21 -0
- data/lib/mark_mapper/plugins/callbacks.rb +42 -0
- data/lib/mark_mapper/plugins/clone.rb +24 -0
- data/lib/mark_mapper/plugins/counter_cache.rb +97 -0
- data/lib/mark_mapper/plugins/dirty.rb +61 -0
- data/lib/mark_mapper/plugins/document.rb +41 -0
- data/lib/mark_mapper/plugins/dumpable.rb +22 -0
- data/lib/mark_mapper/plugins/dynamic_querying.rb +45 -0
- data/lib/mark_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
- data/lib/mark_mapper/plugins/embedded_callbacks.rb +81 -0
- data/lib/mark_mapper/plugins/embedded_document.rb +53 -0
- data/lib/mark_mapper/plugins/equality.rb +23 -0
- data/lib/mark_mapper/plugins/identity_map.rb +144 -0
- data/lib/mark_mapper/plugins/indexable.rb +86 -0
- data/lib/mark_mapper/plugins/inspect.rb +16 -0
- data/lib/mark_mapper/plugins/keys.rb +470 -0
- data/lib/mark_mapper/plugins/keys/key.rb +134 -0
- data/lib/mark_mapper/plugins/keys/static.rb +45 -0
- data/lib/mark_mapper/plugins/logger.rb +18 -0
- data/lib/mark_mapper/plugins/modifiers.rb +140 -0
- data/lib/mark_mapper/plugins/pagination.rb +16 -0
- data/lib/mark_mapper/plugins/partial_updates.rb +77 -0
- data/lib/mark_mapper/plugins/persistence.rb +79 -0
- data/lib/mark_mapper/plugins/protected.rb +45 -0
- data/lib/mark_mapper/plugins/querying.rb +173 -0
- data/lib/mark_mapper/plugins/querying/decorated_markmapper_query.rb +75 -0
- data/lib/mark_mapper/plugins/rails.rb +79 -0
- data/lib/mark_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
- data/lib/mark_mapper/plugins/sci.rb +82 -0
- data/lib/mark_mapper/plugins/scopes.rb +28 -0
- data/lib/mark_mapper/plugins/serialization.rb +109 -0
- data/lib/mark_mapper/plugins/timestamps.rb +29 -0
- data/lib/mark_mapper/plugins/touch.rb +18 -0
- data/lib/mark_mapper/plugins/userstamps.rb +18 -0
- data/lib/mark_mapper/plugins/validations.rb +96 -0
- data/lib/mark_mapper/query.rb +278 -0
- data/lib/mark_mapper/railtie.rb +52 -0
- data/lib/mark_mapper/railtie/database.rake +65 -0
- data/lib/mark_mapper/translation.rb +10 -0
- data/lib/mark_mapper/version.rb +4 -0
- data/lib/rails/generators/mark_mapper/config/config_generator.rb +37 -0
- data/lib/rails/generators/mark_mapper/config/templates/marklogic.yml +19 -0
- data/lib/rails/generators/mark_mapper/model/model_generator.rb +40 -0
- data/lib/rails/generators/mark_mapper/model/templates/model.rb +17 -0
- data/spec/config/mark_mapper.yml +6 -0
- data/spec/examples_spec.rb +25 -0
- data/spec/functional/accessible_spec.rb +198 -0
- data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +64 -0
- data/spec/functional/associations/belongs_to_proxy_spec.rb +255 -0
- data/spec/functional/associations/in_array_proxy_spec.rb +349 -0
- data/spec/functional/associations/many_documents_as_proxy_spec.rb +230 -0
- data/spec/functional/associations/many_documents_proxy_spec.rb +968 -0
- data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +238 -0
- data/spec/functional/associations/many_embedded_proxy_spec.rb +288 -0
- data/spec/functional/associations/many_polymorphic_proxy_spec.rb +302 -0
- data/spec/functional/associations/one_as_proxy_spec.rb +489 -0
- data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +207 -0
- data/spec/functional/associations/one_embedded_proxy_spec.rb +100 -0
- data/spec/functional/associations/one_proxy_spec.rb +406 -0
- data/spec/functional/associations_spec.rb +48 -0
- data/spec/functional/caching_spec.rb +75 -0
- data/spec/functional/callbacks_spec.rb +330 -0
- data/spec/functional/counter_cache_spec.rb +235 -0
- data/spec/functional/dirty_spec.rb +316 -0
- data/spec/functional/document_spec.rb +310 -0
- data/spec/functional/dumpable_spec.rb +24 -0
- data/spec/functional/dynamic_querying_spec.rb +75 -0
- data/spec/functional/embedded_document_spec.rb +316 -0
- data/spec/functional/equality_spec.rb +20 -0
- data/spec/functional/extensions_spec.rb +16 -0
- data/spec/functional/identity_map_spec.rb +483 -0
- data/spec/functional/keys_spec.rb +339 -0
- data/spec/functional/logger_spec.rb +20 -0
- data/spec/functional/modifiers_spec.rb +446 -0
- data/spec/functional/options_hash_spec.rb +41 -0
- data/spec/functional/pagination_spec.rb +89 -0
- data/spec/functional/partial_updates_spec.rb +530 -0
- data/spec/functional/protected_spec.rb +199 -0
- data/spec/functional/querying_spec.rb +984 -0
- data/spec/functional/rails_spec.rb +55 -0
- data/spec/functional/sci_spec.rb +374 -0
- data/spec/functional/scopes_spec.rb +204 -0
- data/spec/functional/static_keys_spec.rb +153 -0
- data/spec/functional/timestamps_spec.rb +97 -0
- data/spec/functional/touch_spec.rb +125 -0
- data/spec/functional/userstamps_spec.rb +46 -0
- data/spec/functional/validations_spec.rb +416 -0
- data/spec/quality_spec.rb +51 -0
- data/spec/spec_helper.rb +150 -0
- data/spec/support/matchers.rb +15 -0
- data/spec/support/models.rb +256 -0
- data/spec/symbol_operator_spec.rb +70 -0
- data/spec/symbol_spec.rb +9 -0
- data/spec/unit/associations/base_spec.rb +146 -0
- data/spec/unit/associations/belongs_to_association_spec.rb +30 -0
- data/spec/unit/associations/many_association_spec.rb +64 -0
- data/spec/unit/associations/one_association_spec.rb +48 -0
- data/spec/unit/associations/proxy_spec.rb +103 -0
- data/spec/unit/clone_spec.rb +79 -0
- data/spec/unit/config_generator_spec.rb +24 -0
- data/spec/unit/criteria_hash_spec.rb +218 -0
- data/spec/unit/document_spec.rb +251 -0
- data/spec/unit/dynamic_finder_spec.rb +125 -0
- data/spec/unit/embedded_document_spec.rb +676 -0
- data/spec/unit/equality_spec.rb +38 -0
- data/spec/unit/exceptions_spec.rb +12 -0
- data/spec/unit/extensions_spec.rb +368 -0
- data/spec/unit/identity_map_middleware_spec.rb +134 -0
- data/spec/unit/inspect_spec.rb +47 -0
- data/spec/unit/key_spec.rb +276 -0
- data/spec/unit/keys_spec.rb +155 -0
- data/spec/unit/mark_mapper_spec.rb +37 -0
- data/spec/unit/model_generator_spec.rb +45 -0
- data/spec/unit/normalizers/criteria_hash_key_spec.rb +37 -0
- data/spec/unit/normalizers/criteria_hash_value_spec.rb +200 -0
- data/spec/unit/normalizers/fields_value_spec.rb +45 -0
- data/spec/unit/normalizers/hash_key_spec.rb +15 -0
- data/spec/unit/normalizers/integer_spec.rb +24 -0
- data/spec/unit/normalizers/options_hash_value_spec.rb +99 -0
- data/spec/unit/normalizers/sort_value_spec.rb +98 -0
- data/spec/unit/options_hash_spec.rb +64 -0
- data/spec/unit/pagination/collection_spec.rb +30 -0
- data/spec/unit/pagination/paginator_spec.rb +118 -0
- data/spec/unit/pagination_spec.rb +11 -0
- data/spec/unit/plugins_spec.rb +89 -0
- data/spec/unit/query_spec.rb +837 -0
- data/spec/unit/rails_compatibility_spec.rb +40 -0
- data/spec/unit/rails_reflect_on_association_spec.rb +118 -0
- data/spec/unit/rails_spec.rb +188 -0
- data/spec/unit/serialization_spec.rb +169 -0
- data/spec/unit/serializers/json_serializer_spec.rb +218 -0
- data/spec/unit/serializers/xml_serializer_spec.rb +198 -0
- data/spec/unit/time_zones_spec.rb +44 -0
- data/spec/unit/translation_spec.rb +27 -0
- data/spec/unit/validations_spec.rb +588 -0
- 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
|