nobrainer 0.15.0 → 0.16.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 +2 -2
- data/lib/no_brainer/criteria.rb +3 -2
- data/lib/no_brainer/criteria/after_find.rb +1 -1
- data/lib/no_brainer/criteria/aggregate.rb +25 -0
- data/lib/no_brainer/criteria/core.rb +15 -1
- data/lib/no_brainer/criteria/count.rb +1 -1
- data/lib/no_brainer/criteria/delete.rb +2 -2
- data/lib/no_brainer/criteria/index.rb +37 -0
- data/lib/no_brainer/criteria/order_by.rb +53 -13
- data/lib/no_brainer/criteria/pluck.rb +81 -0
- data/lib/no_brainer/criteria/raw.rb +12 -4
- data/lib/no_brainer/criteria/scope.rb +11 -6
- data/lib/no_brainer/criteria/update.rb +12 -4
- data/lib/no_brainer/criteria/where.rb +133 -64
- data/lib/no_brainer/document.rb +4 -3
- data/lib/no_brainer/document/aliases.rb +55 -0
- data/lib/no_brainer/document/association.rb +1 -1
- data/lib/no_brainer/document/association/belongs_to.rb +2 -2
- data/lib/no_brainer/document/attributes.rb +26 -8
- data/lib/no_brainer/document/criteria.rb +7 -4
- data/lib/no_brainer/document/dirty.rb +21 -4
- data/lib/no_brainer/document/dynamic_attributes.rb +4 -1
- data/lib/no_brainer/document/index.rb +47 -12
- data/lib/no_brainer/document/lazy_fetch.rb +72 -0
- data/lib/no_brainer/document/missing_attributes.rb +73 -0
- data/lib/no_brainer/document/persistance.rb +57 -8
- data/lib/no_brainer/document/polymorphic.rb +14 -8
- data/lib/no_brainer/document/types.rb +5 -2
- data/lib/no_brainer/document/types/binary.rb +23 -0
- data/lib/no_brainer/document/uniqueness.rb +1 -1
- data/lib/no_brainer/error.rb +16 -0
- data/lib/no_brainer/index_manager.rb +1 -0
- data/lib/no_brainer/query_runner.rb +3 -1
- data/lib/no_brainer/query_runner/connection_lock.rb +7 -0
- data/lib/no_brainer/query_runner/database_on_demand.rb +6 -3
- data/lib/no_brainer/query_runner/logger.rb +22 -6
- data/lib/no_brainer/query_runner/missing_index.rb +5 -3
- data/lib/no_brainer/query_runner/table_on_demand.rb +6 -3
- data/lib/no_brainer/railtie.rb +1 -1
- metadata +12 -4
data/lib/no_brainer/document.rb
CHANGED
@@ -4,9 +4,10 @@ module NoBrainer::Document
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
extend NoBrainer::Autoload
|
6
6
|
|
7
|
-
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
|
8
|
-
:
|
9
|
-
:Criteria, :Polymorphic, :Index
|
7
|
+
autoload_and_include :Core, :StoreIn, :InjectionLayer, :Attributes, :Readonly,
|
8
|
+
:Validation, :Persistance, :Types, :Uniqueness, :Callbacks, :Dirty, :Id,
|
9
|
+
:Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
|
10
|
+
:MissingAttributes, :LazyFetch
|
10
11
|
|
11
12
|
autoload :DynamicAttributes, :Timestamps
|
12
13
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module NoBrainer::Document::Aliases
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
# Index aliases are built-in index.rb
|
5
|
+
|
6
|
+
included do
|
7
|
+
# We ignore polymorphism for aliases.
|
8
|
+
cattr_accessor :alias_map, :alias_reverse_map, :instance_accessor => false
|
9
|
+
self.alias_map = {}
|
10
|
+
self.alias_reverse_map = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def _field(attr, options={})
|
15
|
+
if options[:as]
|
16
|
+
self.alias_map[attr.to_s] = options[:as].to_s
|
17
|
+
self.alias_reverse_map[options[:as].to_s] = attr.to_s
|
18
|
+
end
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def _remove_field(attr, options={})
|
23
|
+
super
|
24
|
+
|
25
|
+
self.alias_map.delete(attr.to_s)
|
26
|
+
self.alias_reverse_map.delete(attr.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reverse_lookup_field_alias(attr)
|
30
|
+
alias_reverse_map[attr.to_s] || attr
|
31
|
+
end
|
32
|
+
|
33
|
+
def lookup_field_alias(attr)
|
34
|
+
alias_map[attr.to_s] || attr
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_fields_reverse_aliased(attrs)
|
38
|
+
case attrs
|
39
|
+
when Array then attrs.map { |k| reverse_lookup_field_alias(k) }
|
40
|
+
when Hash then Hash[attrs.map { |k,v| [reverse_lookup_field_alias(k), v] }]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_fields_aliased(attrs)
|
45
|
+
case attrs
|
46
|
+
when Array then attrs.map { |k| lookup_field_alias(k) }
|
47
|
+
when Hash then Hash[attrs.map { |k,v| [lookup_field_alias(k), v] }]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def persistable_key(k)
|
52
|
+
lookup_field_alias(super)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -2,7 +2,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
2
2
|
include NoBrainer::Document::Association::Core
|
3
3
|
|
4
4
|
class Metadata
|
5
|
-
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :index, :validates, :required]
|
5
|
+
VALID_OPTIONS = [:primary_key, :foreign_key, :class_name, :foreign_key_as, :index, :validates, :required]
|
6
6
|
include NoBrainer::Document::Association::Core::Metadata
|
7
7
|
extend NoBrainer::Document::Association::EagerLoader::Generic
|
8
8
|
|
@@ -29,7 +29,7 @@ class NoBrainer::Document::Association::BelongsTo
|
|
29
29
|
# This would have the effect of loading all the models because they
|
30
30
|
# are likely to be related to each other. So we don't know the type
|
31
31
|
# of the primary key of the target.
|
32
|
-
owner_klass.field(foreign_key, :index => options[:index])
|
32
|
+
owner_klass.field(foreign_key, :as => options[:foreign_key_as], :index => options[:index])
|
33
33
|
owner_klass.validates(target_name, { :presence => true }) if options[:required]
|
34
34
|
owner_klass.validates(target_name, options[:validates]) if options[:validates]
|
35
35
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module NoBrainer::Document::Attributes
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type, :validates, :required, :unique,
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :real_type, :validates, :required, :unique,
|
3
|
+
:in, :readonly, :primary_key, :as, :lazy_fetch]
|
3
4
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations, :pk_value] \
|
4
5
|
+ NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
5
6
|
extend ActiveSupport::Concern
|
@@ -34,9 +35,18 @@ module NoBrainer::Document::Attributes
|
|
34
35
|
end
|
35
36
|
def []=(*args); write_attribute(*args); end
|
36
37
|
|
37
|
-
def assign_defaults
|
38
|
+
def assign_defaults(options)
|
38
39
|
self.class.fields.each do |name, field_options|
|
39
|
-
if field_options.has_key?(:default) &&
|
40
|
+
if field_options.has_key?(:default) &&
|
41
|
+
!@_attributes.has_key?(name)
|
42
|
+
|
43
|
+
if opt = options[:missing_attributes]
|
44
|
+
if (opt[:pluck] && !opt[:pluck][name]) ||
|
45
|
+
(opt[:without] && opt[:without][name])
|
46
|
+
next
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
40
50
|
default_value = field_options[:default]
|
41
51
|
default_value = default_value.call if default_value.is_a?(Proc)
|
42
52
|
self.write_attribute(name, default_value)
|
@@ -45,15 +55,23 @@ module NoBrainer::Document::Attributes
|
|
45
55
|
end
|
46
56
|
|
47
57
|
def assign_attributes(attrs, options={})
|
48
|
-
|
58
|
+
if options[:pristine]
|
59
|
+
if options[:keep_ivars] && options[:missing_attributes].try(:[], :pluck)
|
60
|
+
options[:missing_attributes][:pluck].keys.each { |k| @_attributes.delete(k) }
|
61
|
+
else
|
62
|
+
@_attributes.clear
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
49
66
|
if options[:from_db]
|
67
|
+
attrs = self.class.with_fields_reverse_aliased(attrs)
|
50
68
|
@_attributes.merge!(attrs)
|
51
|
-
clear_dirtiness
|
69
|
+
clear_dirtiness(options)
|
52
70
|
else
|
53
|
-
clear_dirtiness if options[:pristine]
|
71
|
+
clear_dirtiness(options) if options[:pristine]
|
54
72
|
attrs.each { |k,v| self.write_attribute(k,v) }
|
55
73
|
end
|
56
|
-
assign_defaults if options[:pristine]
|
74
|
+
assign_defaults(options) if options[:pristine]
|
57
75
|
self
|
58
76
|
end
|
59
77
|
|
@@ -77,8 +95,8 @@ module NoBrainer::Document::Attributes
|
|
77
95
|
end
|
78
96
|
|
79
97
|
def inherited(subclass)
|
80
|
-
super
|
81
98
|
subclass.fields = self.fields.dup
|
99
|
+
super
|
82
100
|
end
|
83
101
|
|
84
102
|
def _field(attr, options={})
|
@@ -9,17 +9,21 @@ module NoBrainer::Document::Criteria
|
|
9
9
|
|
10
10
|
module ClassMethods
|
11
11
|
delegate :to_rql, # Core
|
12
|
+
:raw, # Raw
|
12
13
|
:limit, :offset, :skip, # Limit
|
13
|
-
:order_by, :reverse_order, :without_ordering, # OrderBy
|
14
|
+
:order_by, :reverse_order, :without_ordering, :order_by_indexed?, :order_by_index_name, # OrderBy
|
14
15
|
:scoped, :unscoped, # Scope
|
15
|
-
:where, :
|
16
|
+
:where, :where_indexed?, :where_index_name, :where_index_type, # Where
|
17
|
+
:with_index, :without_index, :used_index, # Index
|
16
18
|
:with_cache, :without_cache, # Cache
|
17
19
|
:count, :empty?, :any?, # Count
|
18
20
|
:delete_all, :destroy_all, # Delete
|
19
21
|
:includes, :preload, # Preload
|
20
22
|
:each, :to_a, # Enumerable
|
21
23
|
:first, :last, :first!, :last!, :sample, # First
|
24
|
+
:min, :max, :sum, :avg, # Aggregate
|
22
25
|
:update_all, :replace_all, # Update
|
26
|
+
:pluck, :without, :lazy_fetch, :without_plucking, # Pluck
|
23
27
|
:to => :all
|
24
28
|
|
25
29
|
def all
|
@@ -44,8 +48,7 @@ module NoBrainer::Document::Criteria
|
|
44
48
|
rql_table.get(pk)
|
45
49
|
end
|
46
50
|
|
47
|
-
# XXX this doesn't have the same semantics as
|
48
|
-
# other ORMs. the equivalent is find!.
|
51
|
+
# XXX this doesn't have the same semantics as other ORMs. the equivalent is find!.
|
49
52
|
def find(pk)
|
50
53
|
attrs = NoBrainer.run { selector_for(pk) }
|
51
54
|
new_from_db(attrs).tap { |doc| doc.run_callbacks(:find) } if attrs
|
@@ -14,8 +14,14 @@ module NoBrainer::Document::Dirty
|
|
14
14
|
super.tap { clear_dirtiness }
|
15
15
|
end
|
16
16
|
|
17
|
-
def clear_dirtiness
|
18
|
-
|
17
|
+
def clear_dirtiness(options={})
|
18
|
+
if options[:keep_ivars] && options[:missing_attributes].try(:[], :pluck)
|
19
|
+
attrs = options[:missing_attributes][:pluck].keys
|
20
|
+
@_old_attributes = @_old_attributes.reject { |k,v| attrs.include?(k) }
|
21
|
+
else
|
22
|
+
@_old_attributes = {}.with_indifferent_access
|
23
|
+
end
|
24
|
+
|
19
25
|
@_old_attributes_keys = @_attributes.keys # to track undefined -> nil changes
|
20
26
|
end
|
21
27
|
|
@@ -38,7 +44,18 @@ module NoBrainer::Document::Dirty
|
|
38
44
|
result
|
39
45
|
end
|
40
46
|
|
41
|
-
def attribute_may_change(
|
47
|
+
def attribute_may_change(*args)
|
48
|
+
attr = args.first
|
49
|
+
current_value = begin
|
50
|
+
case args.size
|
51
|
+
when 1 then assert_access_field(attr); read_attribute(attr)
|
52
|
+
when 2 then args.last
|
53
|
+
else raise
|
54
|
+
end
|
55
|
+
rescue NoBrainer::Error::MissingAttribute => e
|
56
|
+
e
|
57
|
+
end
|
58
|
+
|
42
59
|
unless @_old_attributes.has_key?(attr)
|
43
60
|
@_old_attributes[attr] = current_value.deep_dup
|
44
61
|
end
|
@@ -74,7 +91,7 @@ module NoBrainer::Document::Dirty
|
|
74
91
|
end
|
75
92
|
|
76
93
|
define_method("#{attr}=") do |value|
|
77
|
-
attribute_may_change(attr
|
94
|
+
attribute_may_change(attr)
|
78
95
|
super(value)
|
79
96
|
end
|
80
97
|
end
|
@@ -5,6 +5,7 @@ module NoBrainer::Document::DynamicAttributes
|
|
5
5
|
if self.respond_to?("#{name}")
|
6
6
|
super
|
7
7
|
else
|
8
|
+
assert_access_field(name)
|
8
9
|
@_attributes[name].tap { |value| attribute_may_change(name, value) if value.respond_to?(:size) }
|
9
10
|
end
|
10
11
|
end
|
@@ -13,8 +14,10 @@ module NoBrainer::Document::DynamicAttributes
|
|
13
14
|
if self.respond_to?("#{name}=")
|
14
15
|
super
|
15
16
|
else
|
16
|
-
attribute_may_change(name
|
17
|
+
attribute_may_change(name)
|
17
18
|
@_attributes[name] = value
|
19
|
+
clear_missing_field(name)
|
20
|
+
value
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module NoBrainer::Document::Index
|
2
|
-
VALID_INDEX_OPTIONS = [:multi]
|
2
|
+
VALID_INDEX_OPTIONS = [:multi, :as]
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
@@ -25,11 +25,17 @@ module NoBrainer::Document::Index
|
|
25
25
|
if name.in?(NoBrainer::Document::Attributes::RESERVED_FIELD_NAMES)
|
26
26
|
raise "Cannot use a reserved field name: #{name}"
|
27
27
|
end
|
28
|
+
|
28
29
|
if has_field?(name) && kind != :single
|
29
30
|
raise "Cannot reuse field name #{name}"
|
30
31
|
end
|
31
32
|
|
32
|
-
|
33
|
+
as = options.delete(:as)
|
34
|
+
as ||= fields[name][:as] if has_field?(name)
|
35
|
+
as ||= name
|
36
|
+
as = as.to_sym
|
37
|
+
|
38
|
+
indexes[name] = {:kind => kind, :what => what, :as => as, :options => options}
|
33
39
|
end
|
34
40
|
|
35
41
|
def remove_index(name)
|
@@ -40,6 +46,10 @@ module NoBrainer::Document::Index
|
|
40
46
|
!!indexes[name.to_sym]
|
41
47
|
end
|
42
48
|
|
49
|
+
def lookup_index_alias(attr)
|
50
|
+
indexes[attr.to_sym].try(:[], :as) || attr
|
51
|
+
end
|
52
|
+
|
43
53
|
def _field(attr, options={})
|
44
54
|
if has_index?(attr) && indexes[attr][:kind] != :single
|
45
55
|
raise "Cannot reuse index attr #{attr}"
|
@@ -47,11 +57,12 @@ module NoBrainer::Document::Index
|
|
47
57
|
|
48
58
|
super
|
49
59
|
|
60
|
+
as = {:as => options[:as]}
|
50
61
|
case options[:index]
|
51
62
|
when nil then
|
52
|
-
when Hash then index(attr, options[:index])
|
53
|
-
when Symbol then index(attr, options[:index] => true)
|
54
|
-
when true then index(attr)
|
63
|
+
when Hash then index(attr, as.merge(options[:index]))
|
64
|
+
when Symbol then index(attr, as.merge(options[:index] => true))
|
65
|
+
when true then index(attr, as)
|
55
66
|
when false then remove_index(attr)
|
56
67
|
end
|
57
68
|
end
|
@@ -66,23 +77,47 @@ module NoBrainer::Document::Index
|
|
66
77
|
index_args = self.indexes[index_name]
|
67
78
|
|
68
79
|
index_proc = case index_args[:kind]
|
69
|
-
when :single then
|
70
|
-
when :compound then ->(doc) { index_args[:what].map { |field| doc[field] } }
|
80
|
+
when :single then ->(doc) { doc[lookup_field_alias(index_name)] }
|
81
|
+
when :compound then ->(doc) { index_args[:what].map { |field| doc[lookup_field_alias(field)] } }
|
71
82
|
when :proc then index_args[:what]
|
72
83
|
end
|
73
84
|
|
74
|
-
NoBrainer.run(self.rql_table.index_create(
|
85
|
+
NoBrainer.run(self.rql_table.index_create(index_args[:as], index_args[:options], &index_proc))
|
75
86
|
wait_for_index(index_name) unless options[:wait] == false
|
76
|
-
|
87
|
+
|
88
|
+
if options[:verbose]
|
89
|
+
if index_name == index_args[:as]
|
90
|
+
STDERR.puts "Created index #{self}.#{index_name}"
|
91
|
+
else
|
92
|
+
STDERR.puts "Created index #{self}.#{index_name} as #{index_args[:as]}"
|
93
|
+
end
|
94
|
+
end
|
77
95
|
end
|
78
96
|
|
79
97
|
def perform_drop_index(index_name, options={})
|
80
|
-
|
81
|
-
|
98
|
+
aliased_name = self.indexes[index_name].try(:[], :as) || index_name
|
99
|
+
NoBrainer.run(self.rql_table.index_drop(aliased_name))
|
100
|
+
|
101
|
+
if options[:verbose]
|
102
|
+
if index_name == index_args[:as]
|
103
|
+
STDERR.puts "Dropped index #{self}.#{index_name}"
|
104
|
+
else
|
105
|
+
STDERR.puts "Dreated index #{self}.#{index_name} as #{index_args[:as]}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_index_alias_reverse_map
|
111
|
+
Hash[self.indexes.map { |k,v| [v[:as], k] }].tap do |mapping|
|
112
|
+
raise "Detected clashing index aliases" if mapping.count != self.indexes.count
|
113
|
+
end
|
82
114
|
end
|
83
115
|
|
84
116
|
def perform_update_indexes(options={})
|
85
|
-
|
117
|
+
alias_mapping = self.get_index_alias_reverse_map
|
118
|
+
current_indexes = NoBrainer.run(self.rql_table.index_list).map do |index|
|
119
|
+
alias_mapping[index.to_sym] || index.to_sym
|
120
|
+
end
|
86
121
|
wanted_indexes = self.indexes.keys - [self.pk_name]
|
87
122
|
|
88
123
|
(current_indexes - wanted_indexes).each do |index_name|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module NoBrainer::Document::LazyFetch
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
singleton_class.send(:attr_accessor, :fields_to_lazy_fetch)
|
6
|
+
self.fields_to_lazy_fetch = Set.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def assign_attributes(attrs, options={})
|
10
|
+
if options[:lazy_fetch].present?
|
11
|
+
lazy_fetch = options[:lazy_fetch]
|
12
|
+
lazy_fetch = lazy_fetch.keys if lazy_fetch.is_a?(Hash)
|
13
|
+
@lazy_fetch = Set.new(lazy_fetch.map(&:to_s))
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def reload(options={})
|
19
|
+
lazy_fetch = self.class.fields_to_lazy_fetch.to_a
|
20
|
+
return super unless lazy_fetch.present?
|
21
|
+
return super if options[:pluck]
|
22
|
+
super(options.merge(:without => lazy_fetch, :lazy_fetch => lazy_fetch))
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def inherited(subclass)
|
27
|
+
subclass.fields_to_lazy_fetch = self.fields_to_lazy_fetch.dup
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def _field(attr, options={})
|
32
|
+
super
|
33
|
+
attr = attr.to_s
|
34
|
+
klass = self
|
35
|
+
inject_in_layer :lazy_fetch do
|
36
|
+
if options[:lazy_fetch]
|
37
|
+
klass.for_each_subclass { |_klass| _klass.fields_to_lazy_fetch << attr }
|
38
|
+
else
|
39
|
+
klass.for_each_subclass { |_klass| _klass.fields_to_lazy_fetch.delete(attr) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Lazy loading can also specified through criteria.
|
43
|
+
define_method("#{attr}") do
|
44
|
+
return super() unless @lazy_fetch
|
45
|
+
|
46
|
+
begin
|
47
|
+
super()
|
48
|
+
rescue NoBrainer::Error::MissingAttribute => e
|
49
|
+
raise e unless attr.in?(@lazy_fetch)
|
50
|
+
reload(:pluck => attr, :keep_ivars => true)
|
51
|
+
@lazy_fetch.delete(attr)
|
52
|
+
retry
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def _remove_field(attr, options={})
|
59
|
+
super
|
60
|
+
for_each_subclass { |klass| klass.fields_to_lazy_fetch.delete(attr) }
|
61
|
+
inject_in_layer :lazy_fetch do
|
62
|
+
remove_method("#{attr}") if method_defined?("#{attr}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def all
|
67
|
+
criteria = super
|
68
|
+
criteria = criteria.lazy_fetch(*self.fields_to_lazy_fetch) if self.fields_to_lazy_fetch.present?
|
69
|
+
criteria
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|