nobrainer 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|