nobrainer 0.22.0 → 0.28.0
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 +4 -4
- data/lib/no_brainer/autoload.rb +1 -1
- data/lib/no_brainer/config.rb +54 -14
- data/lib/no_brainer/connection.rb +24 -21
- data/lib/no_brainer/connection_manager.rb +29 -9
- data/lib/no_brainer/criteria/cache.rb +8 -0
- data/lib/no_brainer/criteria/changes.rb +16 -0
- data/lib/no_brainer/criteria/core.rb +4 -5
- data/lib/no_brainer/criteria/eager_load.rb +2 -2
- data/lib/no_brainer/criteria/enumerable.rb +3 -1
- data/lib/no_brainer/criteria/find.rb +6 -9
- data/lib/no_brainer/criteria/first.rb +3 -3
- data/lib/no_brainer/criteria/first_or_create.rb +114 -0
- data/lib/no_brainer/criteria/join.rb +62 -0
- data/lib/no_brainer/criteria/order_by.rb +19 -16
- data/lib/no_brainer/criteria/run.rb +26 -0
- data/lib/no_brainer/criteria/scope.rb +8 -8
- data/lib/no_brainer/criteria/where.rb +130 -107
- data/lib/no_brainer/criteria.rb +4 -4
- data/lib/no_brainer/document/association/belongs_to.rb +44 -17
- data/lib/no_brainer/document/association/core.rb +11 -0
- data/lib/no_brainer/document/association/eager_loader.rb +26 -26
- data/lib/no_brainer/document/association/has_many.rb +7 -9
- data/lib/no_brainer/document/association/has_many_through.rb +1 -2
- data/lib/no_brainer/document/association/has_one.rb +5 -1
- data/lib/no_brainer/document/association.rb +5 -5
- data/lib/no_brainer/document/atomic_ops.rb +40 -7
- data/lib/no_brainer/document/attributes.rb +24 -15
- data/lib/no_brainer/document/callbacks.rb +1 -1
- data/lib/no_brainer/document/core.rb +18 -18
- data/lib/no_brainer/document/criteria.rb +9 -5
- data/lib/no_brainer/document/dirty.rb +15 -12
- data/lib/no_brainer/document/dynamic_attributes.rb +6 -0
- data/lib/no_brainer/document/index/index.rb +5 -9
- data/lib/no_brainer/document/index/meta_store.rb +1 -10
- data/lib/no_brainer/document/index/synchronizer.rb +16 -20
- data/lib/no_brainer/document/index.rb +3 -3
- data/lib/no_brainer/document/lazy_fetch.rb +3 -3
- data/lib/no_brainer/document/missing_attributes.rb +7 -2
- data/lib/no_brainer/document/persistance.rb +14 -30
- data/lib/no_brainer/document/polymorphic.rb +8 -4
- data/lib/no_brainer/document/primary_key/generator.rb +6 -1
- data/lib/no_brainer/document/primary_key.rb +19 -4
- data/lib/no_brainer/document/serialization.rb +0 -2
- data/lib/no_brainer/document/table_config/synchronizer.rb +21 -0
- data/lib/no_brainer/document/table_config.rb +118 -0
- data/lib/no_brainer/document/timestamps.rb +8 -0
- data/lib/no_brainer/document/validation/core.rb +63 -0
- data/lib/no_brainer/document/validation/uniqueness.rb +30 -36
- data/lib/no_brainer/document/validation.rb +1 -58
- data/lib/no_brainer/document.rb +2 -2
- data/lib/no_brainer/error.rb +1 -0
- data/lib/no_brainer/geo/base.rb +0 -1
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/lock.rb +12 -8
- data/lib/no_brainer/profiler/controller_runtime.rb +76 -0
- data/lib/no_brainer/{query_runner → profiler}/logger.rb +11 -27
- data/lib/no_brainer/profiler.rb +11 -0
- data/lib/no_brainer/query_runner/database_on_demand.rb +8 -8
- data/lib/no_brainer/query_runner/driver.rb +3 -1
- data/lib/no_brainer/query_runner/missing_index.rb +3 -3
- data/lib/no_brainer/query_runner/profiler.rb +43 -0
- data/lib/no_brainer/query_runner/reconnect.rb +38 -23
- data/lib/no_brainer/query_runner/run_options.rb +35 -15
- data/lib/no_brainer/query_runner/table_on_demand.rb +18 -11
- data/lib/no_brainer/query_runner.rb +3 -3
- data/lib/no_brainer/railtie/database.rake +14 -4
- data/lib/no_brainer/railtie.rb +5 -12
- data/lib/no_brainer/rql.rb +11 -8
- data/lib/no_brainer/symbol_decoration.rb +11 -0
- data/lib/no_brainer/system/cluster_config.rb +5 -0
- data/lib/no_brainer/system/db_config.rb +5 -0
- data/lib/no_brainer/system/document.rb +24 -0
- data/lib/no_brainer/system/issue.rb +10 -0
- data/lib/no_brainer/system/job.rb +10 -0
- data/lib/no_brainer/system/log.rb +11 -0
- data/lib/no_brainer/system/server_config.rb +7 -0
- data/lib/no_brainer/system/server_status.rb +9 -0
- data/lib/no_brainer/system/stat.rb +11 -0
- data/lib/no_brainer/system/table_config.rb +10 -0
- data/lib/no_brainer/system/table_status.rb +8 -0
- data/lib/no_brainer/system.rb +17 -0
- data/lib/nobrainer.rb +16 -11
- data/lib/rails/generators/nobrainer/install_generator.rb +48 -0
- data/lib/rails/generators/nobrainer/{model/model_generator.rb → model_generator.rb} +7 -3
- data/lib/rails/generators/nobrainer/namespace_fix.rb +15 -0
- data/lib/rails/generators/{nobrainer/model/templates/model.rb.tt → templates/model.rb} +1 -1
- data/lib/rails/generators/templates/nobrainer.rb +101 -0
- metadata +34 -10
- data/lib/no_brainer/document/store_in.rb +0 -35
- data/lib/rails/generators/nobrainer.rb +0 -18
|
@@ -4,45 +4,72 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
4
4
|
class Metadata
|
|
5
5
|
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :foreign_key_store_as, :index, :validates, :required]
|
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
|
7
|
-
|
|
7
|
+
include NoBrainer::Document::Association::EagerLoader::Generic
|
|
8
8
|
|
|
9
9
|
def foreign_key
|
|
10
|
-
# TODO test :foreign_key
|
|
11
10
|
options[:foreign_key].try(:to_sym) || :"#{target_name}_#{primary_key}"
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def primary_key
|
|
15
|
-
#
|
|
16
|
-
|
|
14
|
+
# We default the primary_key to `:id' and not `target_model.pk_name',
|
|
15
|
+
# because we don't want to require the target_model to be already loaded.
|
|
16
|
+
# (We want the ability to load models in any order).
|
|
17
|
+
# Using target_model.pk_name and allowing lazy loading of models is
|
|
18
|
+
# difficult due to the inexistant API to remove validations if the
|
|
19
|
+
# foreign_key name was to be changed as the pk_name gets renamed.
|
|
20
|
+
return options[:primary_key].to_sym if options[:primary_key]
|
|
21
|
+
|
|
22
|
+
NoBrainer::Document::PrimaryKey::DEFAULT_PK_NAME.tap do |default_pk_name|
|
|
23
|
+
# We'll try to give a warning when we see a different target pk name (best effort).
|
|
24
|
+
real_pk_name = target_model.pk_name rescue nil
|
|
25
|
+
if real_pk_name && real_pk_name != default_pk_name
|
|
26
|
+
raise "Please specify the primary_key name on the following belongs_to association as such:\n" +
|
|
27
|
+
" belongs_to :#{target_name}, :primary_key => :#{real_pk_name}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
17
30
|
end
|
|
18
31
|
|
|
19
32
|
def target_model
|
|
20
|
-
|
|
21
|
-
(options[:class_name] || target_name.to_s.camelize).constantize
|
|
33
|
+
get_model_by_name(options[:class_name] || target_name.to_s.camelize)
|
|
22
34
|
end
|
|
23
35
|
|
|
24
36
|
def base_criteria
|
|
25
|
-
target_model.unscoped
|
|
37
|
+
target_model.without_ordering.unscoped
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
def hook
|
|
29
41
|
super
|
|
30
42
|
|
|
31
|
-
# TODO
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
# TODO set the type of the foreign key to be the same as the target's primary key
|
|
44
|
+
if owner_model.association_metadata.values.any? { |assoc|
|
|
45
|
+
assoc.is_a?(self.class) && assoc != self && assoc.foreign_key == foreign_key }
|
|
46
|
+
raise "Cannot declare `#{target_name}' in #{owner_model}: the foreign_key `#{foreign_key}' is already used"
|
|
47
|
+
end
|
|
48
|
+
|
|
36
49
|
owner_model.field(foreign_key, :store_as => options[:foreign_key_store_as], :index => options[:index])
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
|
|
51
|
+
unless options[:validates] == false
|
|
52
|
+
owner_model.validates(target_name, options[:validates]) if options[:validates]
|
|
53
|
+
|
|
54
|
+
if options[:required]
|
|
55
|
+
owner_model.validates(target_name, :presence => options[:required])
|
|
56
|
+
else
|
|
57
|
+
# Always validate the validity of the foreign_key if not nil.
|
|
58
|
+
owner_model.validates_each(foreign_key) do |doc, attr, value|
|
|
59
|
+
if !value.nil? && doc.read_attribute_for_validation(target_name).nil?
|
|
60
|
+
doc.errors.add(attr, :invalid_foreign_key, :target_model => target_model, :primary_key => primary_key)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
39
65
|
|
|
40
66
|
delegate("#{foreign_key}=", :assign_foreign_key, :call_super => true)
|
|
41
67
|
delegate("#{target_name}_changed?", "#{foreign_key}_changed?", :to => :self)
|
|
42
68
|
add_callback_for(:after_validation)
|
|
43
69
|
end
|
|
44
70
|
|
|
45
|
-
|
|
71
|
+
def eager_load_owner_key; foreign_key; end
|
|
72
|
+
def eager_load_target_key; primary_key; end
|
|
46
73
|
end
|
|
47
74
|
|
|
48
75
|
# Note:
|
|
@@ -58,13 +85,13 @@ class NoBrainer::Document::Association::BelongsTo
|
|
|
58
85
|
return target if loaded?
|
|
59
86
|
|
|
60
87
|
if fk = owner.read_attribute(foreign_key)
|
|
61
|
-
preload(
|
|
88
|
+
preload(base_criteria.where(primary_key => fk).first)
|
|
62
89
|
end
|
|
63
90
|
end
|
|
64
91
|
|
|
65
92
|
def write(target)
|
|
66
93
|
assert_target_type(target)
|
|
67
|
-
owner.write_attribute(foreign_key, target.try(
|
|
94
|
+
owner.write_attribute(foreign_key, target.try(primary_key))
|
|
68
95
|
preload(target)
|
|
69
96
|
end
|
|
70
97
|
|
|
@@ -46,6 +46,17 @@ module NoBrainer::Document::Association::Core
|
|
|
46
46
|
end
|
|
47
47
|
RUBY
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
def get_model_by_name(model_name)
|
|
51
|
+
return model_name if model_name.is_a?(Module)
|
|
52
|
+
|
|
53
|
+
model_name = model_name.to_s
|
|
54
|
+
current_module = @owner_model.parent
|
|
55
|
+
return model_name.constantize if current_module == Object
|
|
56
|
+
return model_name.constantize if model_name =~ /^::/
|
|
57
|
+
return model_name.constantize if !current_module.const_defined?(model_name)
|
|
58
|
+
current_module.const_get(model_name)
|
|
59
|
+
end
|
|
49
60
|
end
|
|
50
61
|
|
|
51
62
|
included { attr_accessor :metadata, :owner }
|
|
@@ -1,31 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
module NoBrainer::Document::Association::EagerLoader
|
|
2
|
+
extend self
|
|
3
|
+
|
|
2
4
|
module Generic
|
|
3
5
|
# Used in associations to declare generic eager loading capabilities
|
|
4
|
-
# The association should implement loaded
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
owner_keys = unloaded_docs.map(&owner_key).compact.uniq
|
|
16
|
-
if owner_keys.present?
|
|
17
|
-
targets = criteria.where(target_key.in => owner_keys)
|
|
18
|
-
.map { |target| [target.read_attribute(target_key), target] }
|
|
19
|
-
.each_with_object(Hash.new { |k,v| k[v] = [] }) { |(k,v),h| h[k] << v }
|
|
20
|
-
|
|
21
|
-
unloaded_docs.each do |doc|
|
|
22
|
-
doc_targets = targets[doc.read_attribute(owner_key)]
|
|
23
|
-
doc.associations[self].preload(doc_targets)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
6
|
+
# The association should implement loaded?, preload,
|
|
7
|
+
# eager_load_owner_key and eager_load_target_key.
|
|
8
|
+
def eager_load(docs, additional_criteria=nil)
|
|
9
|
+
owner_key = eager_load_owner_key
|
|
10
|
+
target_key = eager_load_target_key
|
|
11
|
+
|
|
12
|
+
criteria = base_criteria
|
|
13
|
+
criteria = criteria.merge(additional_criteria) if additional_criteria
|
|
14
|
+
|
|
15
|
+
unloaded_docs = docs.reject { |doc| doc.associations[self].loaded? }
|
|
26
16
|
|
|
27
|
-
|
|
17
|
+
owner_keys = unloaded_docs.map(&owner_key).compact.uniq
|
|
18
|
+
if owner_keys.present?
|
|
19
|
+
targets = criteria.where(target_key.in => owner_keys)
|
|
20
|
+
.map { |target| [target.read_attribute(target_key), target] }
|
|
21
|
+
.each_with_object(Hash.new { |k,v| k[v] = [] }) { |(k,v),h| h[k] << v }
|
|
22
|
+
|
|
23
|
+
unloaded_docs.each do |doc|
|
|
24
|
+
doc_targets = targets[doc.read_attribute(owner_key)]
|
|
25
|
+
doc.associations[self].preload(doc_targets)
|
|
26
|
+
end
|
|
28
27
|
end
|
|
28
|
+
|
|
29
|
+
docs.map { |doc| doc.associations[self].read }.flatten.compact.uniq
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -33,7 +34,6 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
33
34
|
docs = docs.compact
|
|
34
35
|
return [] if docs.empty?
|
|
35
36
|
meta = docs.first.root_class.association_metadata
|
|
36
|
-
# TODO test the singularize thingy.
|
|
37
37
|
association = meta[association_name.to_sym] || meta[association_name.to_s.singularize.to_sym]
|
|
38
38
|
raise "Unknown association #{association_name}" unless association
|
|
39
39
|
association.eager_load(docs, criteria)
|
|
@@ -41,7 +41,7 @@ class NoBrainer::Document::Association::EagerLoader
|
|
|
41
41
|
|
|
42
42
|
def eager_load(docs, what)
|
|
43
43
|
case what
|
|
44
|
-
when Hash
|
|
44
|
+
when Hash then what.each do |k,v|
|
|
45
45
|
if v.is_a?(NoBrainer::Criteria)
|
|
46
46
|
v = v.dup
|
|
47
47
|
nested_preloads, v.options[:eager_load] = v.options[:eager_load], []
|
|
@@ -4,21 +4,18 @@ class NoBrainer::Document::Association::HasMany
|
|
|
4
4
|
class Metadata
|
|
5
5
|
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :dependent, :scope]
|
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
|
7
|
-
|
|
7
|
+
include NoBrainer::Document::Association::EagerLoader::Generic
|
|
8
8
|
|
|
9
9
|
def foreign_key
|
|
10
|
-
|
|
11
|
-
options[:foreign_key].try(:to_sym) || :"#{owner_model.name.underscore}_#{owner_model.pk_name}"
|
|
10
|
+
options[:foreign_key].try(:to_sym) || :"#{owner_model.name.split('::').last.underscore}_#{primary_key}"
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def primary_key
|
|
15
|
-
|
|
16
|
-
options[:primary_key].try(:to_sym) || target_model.pk_name
|
|
14
|
+
options[:primary_key].try(:to_sym) || owner_model.pk_name
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
def target_model
|
|
20
|
-
|
|
21
|
-
(options[:class_name] || target_name.to_s.singularize.camelize).constantize
|
|
18
|
+
get_model_by_name(options[:class_name] || target_name.to_s.singularize.camelize)
|
|
22
19
|
end
|
|
23
20
|
|
|
24
21
|
def base_criteria
|
|
@@ -56,11 +53,12 @@ class NoBrainer::Document::Association::HasMany
|
|
|
56
53
|
end
|
|
57
54
|
end
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
def eager_load_owner_key; primary_key; end
|
|
57
|
+
def eager_load_target_key; foreign_key; end
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
def target_criteria
|
|
63
|
-
@target_criteria ||= base_criteria.where(foreign_key => owner.
|
|
61
|
+
@target_criteria ||= base_criteria.where(foreign_key => owner.__send__(primary_key))
|
|
64
62
|
.after_find(set_inverse_proc)
|
|
65
63
|
end
|
|
66
64
|
|
|
@@ -17,7 +17,7 @@ class NoBrainer::Document::Association::HasManyThrough
|
|
|
17
17
|
def eager_load(docs, additional_criteria=nil)
|
|
18
18
|
criteria = target_model.instance_exec(&options[:scope]) if options[:scope]
|
|
19
19
|
criteria = criteria ? criteria.merge(additional_criteria) : additional_criteria if additional_criteria
|
|
20
|
-
NoBrainer::Document::Association::EagerLoader
|
|
20
|
+
NoBrainer::Document::Association::EagerLoader
|
|
21
21
|
.eager_load_association(through_association.eager_load(docs), target_name, criteria)
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -29,7 +29,6 @@ class NoBrainer::Document::Association::HasManyThrough
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def read
|
|
32
|
-
# TODO implement joins
|
|
33
32
|
@targets ||= metadata.eager_load([owner]).freeze
|
|
34
33
|
end
|
|
35
34
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
class NoBrainer::Document::Association::HasOne < NoBrainer::Document::Association::HasMany
|
|
2
2
|
class Metadata < NoBrainer::Document::Association::HasMany::Metadata
|
|
3
3
|
def target_model
|
|
4
|
-
(options[:class_name] || target_name.to_s.camelize)
|
|
4
|
+
get_model_by_name(options[:class_name] || target_name.to_s.camelize)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def base_criteria
|
|
8
|
+
super.without_ordering
|
|
5
9
|
end
|
|
6
10
|
end
|
|
7
11
|
|
|
@@ -28,11 +28,11 @@ module NoBrainer::Document::Association
|
|
|
28
28
|
raise "Cannot change the :through option" unless r.options[:through] == options[:through]
|
|
29
29
|
r.options.merge!(options)
|
|
30
30
|
else
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
r =
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
class_name = (options[:through] ? "#{association}_through" : association.to_s).camelize
|
|
32
|
+
metadata_class = NoBrainer::Document::Association.const_get(class_name).const_get(:Metadata)
|
|
33
|
+
r = metadata_class.new(self, target, options)
|
|
34
|
+
subclass_tree.each do |subclass|
|
|
35
|
+
subclass.association_metadata[target] = r
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
r.hook
|
|
@@ -42,7 +42,7 @@ module NoBrainer::Document::AtomicOps
|
|
|
42
42
|
def to_s
|
|
43
43
|
"<`#{@field}' with #{@ops.size} pending atomic operations>"
|
|
44
44
|
end
|
|
45
|
-
|
|
45
|
+
def inspect; to_s; end
|
|
46
46
|
|
|
47
47
|
def method_missing(method, *a, &b)
|
|
48
48
|
if method == :<<
|
|
@@ -56,14 +56,20 @@ module NoBrainer::Document::AtomicOps
|
|
|
56
56
|
|
|
57
57
|
def compile_rql_value(rql_doc)
|
|
58
58
|
field = @instance.class.lookup_field_alias(@field)
|
|
59
|
-
|
|
59
|
+
if @is_user_value
|
|
60
|
+
casted_value = @instance.class.cast_model_to_db_for(@field, @value)
|
|
61
|
+
value = RethinkDB::RQL.new.expr(casted_value)
|
|
62
|
+
else
|
|
63
|
+
value = rql_doc[field]
|
|
64
|
+
end
|
|
60
65
|
value = value.default(default_value) if default_value
|
|
61
66
|
@ops.reduce(value) { |v, (method, a, b)| v.__send__(method, *a, &b) }
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
def modify_source!
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
if (@is_user_value && @instance.instance_eval { @_attributes[@field].equal?(@value) }) ||
|
|
71
|
+
!@instance._is_attribute_touched?(@field)
|
|
72
|
+
@instance._write_attribute(@field, self)
|
|
67
73
|
end
|
|
68
74
|
end
|
|
69
75
|
end
|
|
@@ -125,6 +131,18 @@ module NoBrainer::Document::AtomicOps
|
|
|
125
131
|
end
|
|
126
132
|
end
|
|
127
133
|
|
|
134
|
+
class PendingAtomicUnset < PendingAtomic
|
|
135
|
+
def to_s
|
|
136
|
+
"<unset `#{@field}'>"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
undef_method(:method_missing)
|
|
140
|
+
|
|
141
|
+
def compile_rql_value(rql_doc)
|
|
142
|
+
RethinkDB::RQL.new.literal()
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
128
146
|
def clear_dirtiness(options={})
|
|
129
147
|
super
|
|
130
148
|
@_touched_attributes = Set.new
|
|
@@ -166,13 +184,18 @@ module NoBrainer::Document::AtomicOps
|
|
|
166
184
|
end
|
|
167
185
|
|
|
168
186
|
def _read_attribute(name)
|
|
187
|
+
return super if name == self.class.pk_name
|
|
188
|
+
|
|
169
189
|
ensure_exclusive_atomic!
|
|
170
|
-
|
|
171
|
-
|
|
190
|
+
return super unless in_atomic?
|
|
191
|
+
|
|
192
|
+
# If we are missing fields, it's okay, we'll assume nil.
|
|
193
|
+
value = missing_field?(name) ? nil : super
|
|
172
194
|
|
|
173
195
|
case value
|
|
174
196
|
when PendingAtomicContainer then value
|
|
175
|
-
when
|
|
197
|
+
when PendingAtomicUnset then raise "Attribute `#{name}' is unset"
|
|
198
|
+
when PendingAtomic then value.dup
|
|
176
199
|
else PendingAtomic._new(self, name, value, _is_attribute_touched?(name))
|
|
177
200
|
end
|
|
178
201
|
end
|
|
@@ -193,6 +216,11 @@ module NoBrainer::Document::AtomicOps
|
|
|
193
216
|
super
|
|
194
217
|
end
|
|
195
218
|
|
|
219
|
+
def unset(attr)
|
|
220
|
+
return queue_atomic { unset(attr) } unless in_atomic?
|
|
221
|
+
_write_attribute(attr, PendingAtomicUnset.new(self, attr, nil, true, nil))
|
|
222
|
+
end
|
|
223
|
+
|
|
196
224
|
def save?(options={})
|
|
197
225
|
# TODO allow reload => true as an option to save+reload in a single op.
|
|
198
226
|
raise NoBrainer::Error::AtomicBlock.new('You may persist documents only outside of queue_atomic blocks') if in_atomic?
|
|
@@ -206,6 +234,11 @@ module NoBrainer::Document::AtomicOps
|
|
|
206
234
|
end
|
|
207
235
|
end
|
|
208
236
|
|
|
237
|
+
def reload(*)
|
|
238
|
+
raise NoBrainer::Error::AtomicBlock.new('You may not reload fields within an atomic block') if in_atomic?
|
|
239
|
+
super
|
|
240
|
+
end
|
|
241
|
+
|
|
209
242
|
module ClassMethods
|
|
210
243
|
def persistable_value(k, v, options={})
|
|
211
244
|
v.is_a?(PendingAtomic) ? v.compile_rql_value(options[:rql_doc]) : super
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
module NoBrainer::Document::Attributes
|
|
2
2
|
VALID_FIELD_OPTIONS = [:index, :default, :type, :readonly, :primary_key, :lazy_fetch, :store_as,
|
|
3
3
|
:validates, :required, :unique, :uniq, :format, :in, :length, :min_length, :max_length]
|
|
4
|
-
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value]
|
|
5
|
-
|
|
4
|
+
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] +
|
|
5
|
+
NoBrainer::SymbolDecoration::OPERATORS
|
|
6
|
+
|
|
6
7
|
extend ActiveSupport::Concern
|
|
7
8
|
include ActiveModel::ForbiddenAttributesProtection
|
|
8
9
|
|
|
@@ -48,8 +49,10 @@ module NoBrainer::Document::Attributes
|
|
|
48
49
|
|
|
49
50
|
def assign_defaults(options)
|
|
50
51
|
self.class.fields.each do |name, field_options|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
# :default => nil will not set the value to nil, but :default => ->{ nil } will.
|
|
53
|
+
# This is useful to unset a default value.
|
|
54
|
+
|
|
55
|
+
next if field_options[:default].nil? || @_attributes.key?(name)
|
|
53
56
|
|
|
54
57
|
if opt = options[:missing_attributes]
|
|
55
58
|
if (opt[:pluck] && !opt[:pluck][name]) ||
|
|
@@ -112,8 +115,14 @@ module NoBrainer::Document::Attributes
|
|
|
112
115
|
super
|
|
113
116
|
end
|
|
114
117
|
|
|
118
|
+
# The different between _field and field is that field can set other options
|
|
119
|
+
# (c.f. primary key module). _field always receive an immutable options list.
|
|
115
120
|
def _field(attr, options={})
|
|
116
|
-
|
|
121
|
+
options.assert_valid_keys(*VALID_FIELD_OPTIONS)
|
|
122
|
+
if attr.in?(RESERVED_FIELD_NAMES)
|
|
123
|
+
raise "The field name `:#{attr}' is reserved. Please use another one."
|
|
124
|
+
end
|
|
125
|
+
|
|
117
126
|
attr = attr.to_s
|
|
118
127
|
inject_in_layer :attributes do
|
|
119
128
|
define_method("#{attr}=") { |value| _write_attribute(attr, value) }
|
|
@@ -124,14 +133,9 @@ module NoBrainer::Document::Attributes
|
|
|
124
133
|
def field(attr, options={})
|
|
125
134
|
attr = attr.to_sym
|
|
126
135
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
([self] + descendants).each do |model|
|
|
133
|
-
model.fields[attr] ||= {}
|
|
134
|
-
model.fields[attr].deep_merge!(options)
|
|
136
|
+
subclass_tree.each do |subclass|
|
|
137
|
+
subclass.fields[attr] ||= {}
|
|
138
|
+
subclass.fields[attr].deep_merge!(options)
|
|
135
139
|
end
|
|
136
140
|
|
|
137
141
|
_field(attr, self.fields[attr])
|
|
@@ -149,13 +153,18 @@ module NoBrainer::Document::Attributes
|
|
|
149
153
|
|
|
150
154
|
_remove_field(attr, options)
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
subclass_tree.each do |subclass|
|
|
157
|
+
subclass.fields.delete(attr)
|
|
154
158
|
end
|
|
155
159
|
end
|
|
156
160
|
|
|
157
161
|
def has_field?(attr)
|
|
158
162
|
!!fields[attr.to_sym]
|
|
159
163
|
end
|
|
164
|
+
|
|
165
|
+
def ensure_valid_key!(key)
|
|
166
|
+
return if has_field?(key) || has_index?(key)
|
|
167
|
+
raise NoBrainer::Error::UnknownAttribute, "`#{key}' is not a valid attribute of #{self}"
|
|
168
|
+
end
|
|
160
169
|
end
|
|
161
170
|
end
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
module NoBrainer::Document::Core
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
singleton_class.class_eval
|
|
5
|
-
attr_accessor :_all, :_all_nobrainer
|
|
6
|
-
|
|
7
|
-
def all
|
|
8
|
-
Rails.application.eager_load! if defined?(Rails.application.eager_load!)
|
|
9
|
-
@_all
|
|
10
|
-
end
|
|
11
|
-
end
|
|
4
|
+
singleton_class.class_eval { attr_accessor :_all }
|
|
12
5
|
self._all = []
|
|
13
|
-
self._all_nobrainer = []
|
|
14
|
-
|
|
15
|
-
include ActiveModel::Conversion
|
|
16
|
-
|
|
17
|
-
def to_key
|
|
18
|
-
# ActiveModel::Conversion stuff
|
|
19
|
-
[pk_value]
|
|
20
|
-
end
|
|
21
6
|
|
|
22
7
|
included do
|
|
23
8
|
# TODO test these includes
|
|
24
9
|
extend ActiveModel::Naming
|
|
25
10
|
extend ActiveModel::Translation
|
|
26
11
|
|
|
27
|
-
|
|
28
|
-
|
|
12
|
+
NoBrainer::Document::Core._all << self unless name =~ /^NoBrainer::/
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.all(options={})
|
|
16
|
+
(options[:types] || [:user]).map do |type|
|
|
17
|
+
case type
|
|
18
|
+
when :user
|
|
19
|
+
Rails.application.eager_load! if defined?(Rails.application.eager_load!)
|
|
20
|
+
_all
|
|
21
|
+
when :nobrainer
|
|
22
|
+
[NoBrainer::Document::Index::MetaStore, NoBrainer::Lock]
|
|
23
|
+
when :system
|
|
24
|
+
NoBrainer::System.constants
|
|
25
|
+
.map { |c| NoBrainer::System.const_get(c) }
|
|
26
|
+
.select { |m| m < NoBrainer::Document }
|
|
27
|
+
end
|
|
28
|
+
end.reduce([], &:+)
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -17,8 +17,8 @@ module NoBrainer::Document::Criteria
|
|
|
17
17
|
:raw, # Raw
|
|
18
18
|
:limit, :offset, :skip, # Limit
|
|
19
19
|
:order_by, :reverse_order, :without_ordering, :order_by_indexed?, :order_by_index_name, # OrderBy
|
|
20
|
-
:
|
|
21
|
-
:where, :where_indexed?, :where_index_name, :where_index_type, # Where
|
|
20
|
+
:unscoped, # Scope
|
|
21
|
+
:_where, :where, :where_indexed?, :where_index_name, :where_index_type, # Where
|
|
22
22
|
:with_index, :without_index, :used_index, # Index
|
|
23
23
|
:with_cache, :without_cache, # Cache
|
|
24
24
|
:count, :empty?, :any?, # Count
|
|
@@ -26,14 +26,18 @@ module NoBrainer::Document::Criteria
|
|
|
26
26
|
:preload, :eager_load, # EagerLoad
|
|
27
27
|
:each, :to_a, # Enumerable
|
|
28
28
|
:first, :last, :first!, :last!, :sample, # First
|
|
29
|
+
:upsert, :upsert!, :first_or_create, :first_or_create!, # FirstOrCreate
|
|
29
30
|
:min, :max, :sum, :avg, # Aggregate
|
|
30
31
|
:update_all, :replace_all, # Update
|
|
32
|
+
:changes, # Changes
|
|
31
33
|
:pluck, :without, :lazy_fetch, :without_plucking, # Pluck
|
|
32
34
|
:find_by?, :find_by, :find_by!, :find?, :find, :find!, # Find
|
|
35
|
+
:join, #Join
|
|
33
36
|
:to => :all
|
|
34
37
|
|
|
35
38
|
def all
|
|
36
|
-
NoBrainer::Criteria.new(:
|
|
39
|
+
NoBrainer::Criteria.new(:initial_run_options => NoBrainer.current_run_options,
|
|
40
|
+
:model => self)
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
def scope(name, criteria=nil, &block)
|
|
@@ -48,8 +52,8 @@ module NoBrainer::Document::Criteria
|
|
|
48
52
|
criteria_proc = block || (criteria.is_a?(Proc) ? criteria : proc { criteria })
|
|
49
53
|
raise "default_scope only accepts a criteria or a proc that returns criteria" unless criteria_proc.is_a?(Proc)
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
subclass_tree.each do |subclass|
|
|
56
|
+
subclass.default_scopes << criteria_proc
|
|
53
57
|
end
|
|
54
58
|
end
|
|
55
59
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
module NoBrainer::Document::Dirty
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
|
-
# We
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
3
|
+
# 1) We should save the changes as seen through read_attribute, because the
|
|
4
|
+
# user sees attributes through the read_attribute getters, but it's near
|
|
5
|
+
# impossible because we would need to wrap the user defined getters, so we'll
|
|
6
|
+
# go through _read_attribute.
|
|
7
|
+
# 2) We want to detect changes based on @_attributes to track things like
|
|
8
|
+
# undefined -> nil. Going through the getters will not give us that.
|
|
8
9
|
|
|
9
10
|
def _create(*args)
|
|
10
11
|
super.tap { clear_dirtiness }
|
|
@@ -17,7 +18,9 @@ module NoBrainer::Document::Dirty
|
|
|
17
18
|
def clear_dirtiness(options={})
|
|
18
19
|
if options[:keep_ivars] && options[:missing_attributes].try(:[], :pluck)
|
|
19
20
|
attrs = options[:missing_attributes][:pluck].keys
|
|
20
|
-
@_old_attributes = @_old_attributes.reject { |k,v| attrs.include?(k) }
|
|
21
|
+
@_old_attributes = @_old_attributes.reject { |k,v| attrs.include?(k) }
|
|
22
|
+
else
|
|
23
|
+
@_old_attributes = {}.with_indifferent_access
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
@_old_attributes_keys = @_attributes.keys # to track undefined -> nil changes
|
|
@@ -34,7 +37,7 @@ module NoBrainer::Document::Dirty
|
|
|
34
37
|
def changes
|
|
35
38
|
result = {}.with_indifferent_access
|
|
36
39
|
@_old_attributes.each do |attr, old_value|
|
|
37
|
-
current_value =
|
|
40
|
+
current_value = _read_attribute(attr)
|
|
38
41
|
if current_value != old_value || !@_old_attributes_keys.include?(attr)
|
|
39
42
|
result[attr] = [old_value, current_value]
|
|
40
43
|
end
|
|
@@ -47,13 +50,13 @@ module NoBrainer::Document::Dirty
|
|
|
47
50
|
if current_value == None
|
|
48
51
|
current_value = begin
|
|
49
52
|
assert_access_field(attr)
|
|
50
|
-
|
|
53
|
+
_read_attribute(attr)
|
|
51
54
|
rescue NoBrainer::Error::MissingAttribute => e
|
|
52
55
|
e
|
|
53
56
|
end
|
|
54
57
|
end
|
|
55
58
|
|
|
56
|
-
unless @_old_attributes.
|
|
59
|
+
unless @_old_attributes.key?(attr)
|
|
57
60
|
@_old_attributes[attr] = current_value.deep_dup
|
|
58
61
|
end
|
|
59
62
|
end
|
|
@@ -78,8 +81,8 @@ module NoBrainer::Document::Dirty
|
|
|
78
81
|
|
|
79
82
|
inject_in_layer :dirty_tracking do
|
|
80
83
|
define_method("#{attr}_change") do
|
|
81
|
-
if @_old_attributes.
|
|
82
|
-
result = [@_old_attributes[attr],
|
|
84
|
+
if @_old_attributes.key?(attr)
|
|
85
|
+
result = [@_old_attributes[attr], _read_attribute(attr)]
|
|
83
86
|
result if result.first != result.last || !@_old_attributes_keys.include?(attr)
|
|
84
87
|
end
|
|
85
88
|
end
|
|
@@ -89,7 +92,7 @@ module NoBrainer::Document::Dirty
|
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
define_method("#{attr}_was") do
|
|
92
|
-
@_old_attributes.
|
|
95
|
+
@_old_attributes.key?(attr) ? @_old_attributes[attr] : _read_attribute(attr)
|
|
93
96
|
end
|
|
94
97
|
end
|
|
95
98
|
end
|