nobrainer 0.10.0 → 0.13.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/LICENSE +169 -0
- data/lib/no_brainer/autoload.rb +1 -1
- data/lib/no_brainer/criteria/after_find.rb +24 -0
- data/lib/no_brainer/criteria/{termination/cache.rb → cache.rb} +1 -1
- data/lib/no_brainer/criteria/{chainable/core.rb → core.rb} +2 -2
- data/lib/no_brainer/criteria/{termination/count.rb → count.rb} +1 -1
- data/lib/no_brainer/criteria/{termination/delete.rb → delete.rb} +2 -2
- data/lib/no_brainer/criteria/{termination/enumerable.rb → enumerable.rb} +1 -1
- data/lib/no_brainer/criteria/{termination/first.rb → first.rb} +1 -1
- data/lib/no_brainer/criteria/{chainable/limit.rb → limit.rb} +1 -1
- data/lib/no_brainer/criteria/{chainable/order_by.rb → order_by.rb} +1 -1
- data/lib/no_brainer/criteria/preload.rb +55 -0
- data/lib/no_brainer/criteria/raw.rb +25 -0
- data/lib/no_brainer/criteria/{chainable/scope.rb → scope.rb} +1 -1
- data/lib/no_brainer/criteria/update.rb +11 -0
- data/lib/no_brainer/criteria/{chainable/where.rb → where.rb} +61 -25
- data/lib/no_brainer/criteria.rb +3 -16
- data/lib/no_brainer/document/association/belongs_to.rb +10 -2
- data/lib/no_brainer/document/association/core.rb +2 -2
- data/lib/no_brainer/document/association/eager_loader.rb +7 -7
- data/lib/no_brainer/document/association/has_many.rb +1 -1
- data/lib/no_brainer/document/attributes.rb +30 -44
- data/lib/no_brainer/document/callbacks.rb +29 -0
- data/lib/no_brainer/document/core.rb +0 -2
- data/lib/no_brainer/document/criteria.rb +1 -2
- data/lib/no_brainer/document/dirty.rb +52 -52
- data/lib/no_brainer/document/dynamic_attributes.rb +11 -3
- data/lib/no_brainer/document/id.rb +1 -1
- data/lib/no_brainer/document/index.rb +7 -14
- data/lib/no_brainer/document/injection_layer.rb +2 -3
- data/lib/no_brainer/document/persistance.rb +29 -43
- data/lib/no_brainer/document/readonly.rb +19 -0
- data/lib/no_brainer/document/serialization.rb +0 -1
- data/lib/no_brainer/document/timestamps.rb +9 -9
- data/lib/no_brainer/document/types.rb +46 -41
- data/lib/no_brainer/document/validation.rb +6 -0
- data/lib/no_brainer/document.rb +4 -4
- data/lib/no_brainer/error.rb +37 -9
- data/lib/no_brainer/query_runner/connection.rb +36 -9
- data/lib/no_brainer/query_runner/missing_index.rb +1 -1
- data/lib/no_brainer/query_runner/run_options.rb +4 -2
- data/lib/no_brainer/railtie.rb +10 -0
- data/lib/nobrainer.rb +3 -7
- metadata +54 -44
- data/LICENSE.md +0 -7
- data/lib/no_brainer/criteria/chainable/raw.rb +0 -33
- data/lib/no_brainer/criteria/termination/eager_loading.rb +0 -50
- data/lib/no_brainer/criteria/termination/inc.rb +0 -14
- data/lib/no_brainer/criteria/termination/update.rb +0 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module NoBrainer::Document::Attributes
|
|
2
|
-
VALID_FIELD_OPTIONS = [:index, :default, :type]
|
|
2
|
+
VALID_FIELD_OPTIONS = [:index, :default, :type, :type_cast_method, :validates, :required, :readonly]
|
|
3
3
|
RESERVED_FIELD_NAMES = [:index, :default, :and, :or, :selector, :associations] + NoBrainer::DecoratedSymbol::MODIFIERS.keys
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
|
|
@@ -10,14 +10,13 @@ module NoBrainer::Document::Attributes
|
|
|
10
10
|
self.fields = {}
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
@attributes = {}
|
|
13
|
+
def _initialize(attrs={}, options={})
|
|
14
|
+
@_attributes = {}.with_indifferent_access
|
|
16
15
|
assign_attributes(attrs, options.reverse_merge(:pristine => true))
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def attributes
|
|
20
|
-
@
|
|
19
|
+
Hash[@_attributes.keys.map { |k| [k, read_attribute(k)] }].with_indifferent_access.freeze
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def read_attribute(name)
|
|
@@ -32,7 +31,7 @@ module NoBrainer::Document::Attributes
|
|
|
32
31
|
|
|
33
32
|
def assign_defaults
|
|
34
33
|
self.class.fields.each do |name, field_options|
|
|
35
|
-
if field_options.has_key?(:default) && !@
|
|
34
|
+
if field_options.has_key?(:default) && !@_attributes.has_key?(name)
|
|
36
35
|
default_value = field_options[:default]
|
|
37
36
|
default_value = default_value.call if default_value.is_a?(Proc)
|
|
38
37
|
self.write_attribute(name, default_value)
|
|
@@ -40,13 +39,13 @@ module NoBrainer::Document::Attributes
|
|
|
40
39
|
end
|
|
41
40
|
end
|
|
42
41
|
|
|
43
|
-
def _assign_attributes(attrs, options={})
|
|
44
|
-
attrs.each { |k,v| self.write_attribute(k,v) }
|
|
45
|
-
end
|
|
46
|
-
|
|
47
42
|
def assign_attributes(attrs, options={})
|
|
48
|
-
@
|
|
49
|
-
|
|
43
|
+
@_attributes.clear if options[:pristine]
|
|
44
|
+
if options[:from_db]
|
|
45
|
+
@_attributes.merge!(attrs)
|
|
46
|
+
else
|
|
47
|
+
attrs.each { |k,v| self.write_attribute(k,v) }
|
|
48
|
+
end
|
|
50
49
|
assign_defaults if options[:pristine]
|
|
51
50
|
self
|
|
52
51
|
end
|
|
@@ -54,7 +53,7 @@ module NoBrainer::Document::Attributes
|
|
|
54
53
|
|
|
55
54
|
def inspectable_attributes
|
|
56
55
|
# TODO test that thing
|
|
57
|
-
Hash[@
|
|
56
|
+
Hash[@_attributes.sort_by { |k,v| self.class.fields.keys.index(k.to_sym) || 2**10 }]
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
def inspect
|
|
@@ -72,46 +71,33 @@ module NoBrainer::Document::Attributes
|
|
|
72
71
|
subclass.fields = self.fields.dup
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
def
|
|
76
|
-
name = name.to_sym
|
|
77
|
-
|
|
78
|
-
options.assert_valid_keys(*VALID_FIELD_OPTIONS)
|
|
79
|
-
if name.in?(RESERVED_FIELD_NAMES)
|
|
80
|
-
raise "Cannot use a reserved field name: #{name}"
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
([self] + descendants).each do |klass|
|
|
84
|
-
klass.fields[name] ||= {}
|
|
85
|
-
klass.fields[name].merge!(options)
|
|
86
|
-
end
|
|
87
|
-
|
|
74
|
+
def _field(attr, options={})
|
|
88
75
|
# Using a layer so the user can use super when overriding these methods
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def #{name}
|
|
95
|
-
@attributes['#{name}']
|
|
96
|
-
end
|
|
97
|
-
RUBY
|
|
76
|
+
attr = attr.to_s
|
|
77
|
+
inject_in_layer :attributes do
|
|
78
|
+
define_method("#{attr}=") { |value| @_attributes[attr] = value }
|
|
79
|
+
define_method("#{attr}") { @_attributes[attr] }
|
|
80
|
+
end
|
|
98
81
|
end
|
|
99
82
|
|
|
100
|
-
def
|
|
101
|
-
|
|
83
|
+
def field(attr, options={})
|
|
84
|
+
attr = attr.to_sym
|
|
85
|
+
|
|
86
|
+
options.assert_valid_keys(*VALID_FIELD_OPTIONS)
|
|
87
|
+
if attr.in?(RESERVED_FIELD_NAMES)
|
|
88
|
+
raise "Cannot use a reserved field attr: #{attr}"
|
|
89
|
+
end
|
|
102
90
|
|
|
103
91
|
([self] + descendants).each do |klass|
|
|
104
|
-
klass.fields
|
|
92
|
+
klass.fields[attr] ||= {}
|
|
93
|
+
klass.fields[attr].deep_merge!(options)
|
|
105
94
|
end
|
|
106
95
|
|
|
107
|
-
|
|
108
|
-
undef #{name}=
|
|
109
|
-
undef #{name}
|
|
110
|
-
RUBY
|
|
96
|
+
_field(attr, self.fields[attr])
|
|
111
97
|
end
|
|
112
98
|
|
|
113
|
-
def has_field?(
|
|
114
|
-
!!fields[
|
|
99
|
+
def has_field?(attr)
|
|
100
|
+
!!fields[attr.to_sym]
|
|
115
101
|
end
|
|
116
102
|
end
|
|
117
103
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module NoBrainer::Document::Callbacks
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
extend ActiveModel::Callbacks
|
|
6
|
+
define_model_callbacks :initialize, :create, :update, :save, :destroy, :terminator => 'false'
|
|
7
|
+
define_model_callbacks :find, :only => [:after], :terminator => 'false'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(*args, &block)
|
|
11
|
+
run_callbacks(:initialize) { _initialize(*args); true }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def _create(*args, &block)
|
|
15
|
+
run_callbacks(:create) { super }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def _update_only_changed_attrs(*args, &block)
|
|
19
|
+
run_callbacks(:update) { super }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def save(*args, &block)
|
|
23
|
+
run_callbacks(:save) { super }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def destroy(*args, &block)
|
|
27
|
+
run_callbacks(:destroy) { super }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -16,10 +16,9 @@ module NoBrainer::Document::Criteria
|
|
|
16
16
|
:with_cache, :without_cache, # Cache
|
|
17
17
|
:count, :empty?, :any?, # Count
|
|
18
18
|
:delete_all, :destroy_all, # Delete
|
|
19
|
-
:includes,
|
|
19
|
+
:includes, :preload, # Preload
|
|
20
20
|
:each, :to_a, # Enumerable
|
|
21
21
|
:first, :last, :first!, :last!, # First
|
|
22
|
-
:inc_all, :dec_all, # Inc
|
|
23
22
|
:update_all, :replace_all, # Update
|
|
24
23
|
:to => :all
|
|
25
24
|
|
|
@@ -1,88 +1,88 @@
|
|
|
1
1
|
module NoBrainer::Document::Dirty
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
|
+
# We need to save the changes as seen through read_attribute because
|
|
4
|
+
# the user sees attributes through the read_attribute getters.
|
|
5
|
+
# But we want to detect changes based on @_attributes to track
|
|
6
|
+
# things like undefined -> nil. Going through the getters will
|
|
7
|
+
# not give us that.
|
|
3
8
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# attributes keys. We don't want that.
|
|
8
|
-
|
|
9
|
-
included do
|
|
10
|
-
attr_accessor :previous_changes
|
|
11
|
-
after_save { clear_dirtiness }
|
|
9
|
+
def assign_attributes(attrs, options={})
|
|
10
|
+
clear_dirtiness if options[:pristine]
|
|
11
|
+
super
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
|
|
14
|
+
def _create(*args)
|
|
15
|
+
super.tap { clear_dirtiness }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def
|
|
19
|
-
super
|
|
20
|
-
clear_dirtiness if options[:pristine]
|
|
18
|
+
def _update(*args)
|
|
19
|
+
super.tap { clear_dirtiness }
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def clear_dirtiness
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
@_old_attributes = {}.with_indifferent_access
|
|
24
|
+
@_old_attributes_keys = @_attributes.keys # to track undefined -> nil changes
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def changed?
|
|
29
|
-
|
|
28
|
+
changes.present?
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def changed
|
|
33
|
-
|
|
32
|
+
changes.keys
|
|
34
33
|
end
|
|
35
34
|
|
|
36
35
|
def changes
|
|
37
|
-
|
|
36
|
+
result = {}.with_indifferent_access
|
|
37
|
+
@_old_attributes.each do |attr, old_value|
|
|
38
|
+
current_value = read_attribute(attr)
|
|
39
|
+
if current_value != old_value || !@_old_attributes_keys.include?(attr)
|
|
40
|
+
result[attr] = [old_value, current_value]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
result
|
|
38
44
|
end
|
|
39
45
|
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# happened. Why is that?
|
|
45
|
-
value = read_attribute(attr)
|
|
46
|
-
value = value.clone if value.duplicable?
|
|
47
|
-
|
|
48
|
-
return if value == new_value
|
|
49
|
-
|
|
50
|
-
changed_attributes[attr] = value
|
|
46
|
+
def attribute_may_change(attr, current_value)
|
|
47
|
+
unless @_old_attributes.has_key?(attr)
|
|
48
|
+
@_old_attributes[attr] = current_value.deep_dup
|
|
49
|
+
end
|
|
51
50
|
end
|
|
52
51
|
|
|
53
52
|
module ClassMethods
|
|
54
|
-
def
|
|
53
|
+
def _field(attr, options={})
|
|
55
54
|
super
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
attr = attr.to_s
|
|
56
|
+
|
|
57
|
+
inject_in_layer :dirty_tracking do
|
|
58
|
+
define_method("#{attr}_change") do
|
|
59
|
+
if @_old_attributes.has_key?(attr)
|
|
60
|
+
result = [@_old_attributes[attr], read_attribute(attr)]
|
|
61
|
+
result if result.first != result.last || !@_old_attributes_keys.include?(attr)
|
|
62
|
+
end
|
|
60
63
|
end
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
define_method("#{attr}_changed?") do
|
|
66
|
+
!!__send__("#{attr}_change")
|
|
64
67
|
end
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
define_method("#{attr}_was") do
|
|
70
|
+
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute(attr)
|
|
68
71
|
end
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
define_method("#{attr}") do
|
|
74
|
+
super().tap do |value|
|
|
75
|
+
# This take care of string/arrays/hashes that could change without going
|
|
76
|
+
# through the setter.
|
|
77
|
+
attribute_may_change(attr, value) if value.respond_to?(:size)
|
|
78
|
+
end
|
|
73
79
|
end
|
|
74
|
-
RUBY
|
|
75
|
-
end
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
undef #{name}_change
|
|
83
|
-
undef #{name}_was
|
|
84
|
-
undef #{name}=
|
|
85
|
-
RUBY
|
|
81
|
+
define_method("#{attr}=") do |value|
|
|
82
|
+
attribute_may_change(attr, read_attribute(attr))
|
|
83
|
+
super(value)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
88
|
end
|
|
@@ -2,11 +2,19 @@ module NoBrainer::Document::DynamicAttributes
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
def read_attribute(name)
|
|
5
|
-
self.respond_to?("#{name}")
|
|
5
|
+
if self.respond_to?("#{name}")
|
|
6
|
+
super
|
|
7
|
+
else
|
|
8
|
+
@_attributes[name].tap { |value| attribute_may_change(name, value) if value.respond_to?(:size) }
|
|
9
|
+
end
|
|
6
10
|
end
|
|
7
11
|
|
|
8
12
|
def write_attribute(name, value)
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
if self.respond_to?("#{name}=")
|
|
14
|
+
super
|
|
15
|
+
else
|
|
16
|
+
attribute_may_change(name, read_attribute(name))
|
|
17
|
+
@_attributes[name] = value
|
|
18
|
+
end
|
|
11
19
|
end
|
|
12
20
|
end
|
|
@@ -6,7 +6,7 @@ module NoBrainer::Document::Id
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
|
-
self.field :id, :default => ->{ NoBrainer::Document::Id.generate }
|
|
9
|
+
self.field :id, :type => String, :default => ->{ NoBrainer::Document::Id.generate }, :readonly => true
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def ==(other)
|
|
@@ -41,29 +41,22 @@ module NoBrainer::Document::Index
|
|
|
41
41
|
!!indexes[name.to_sym]
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if has_index?(name) && indexes[name][:kind] != :single
|
|
48
|
-
raise "Cannot reuse index name #{name}"
|
|
44
|
+
def _field(attr, options={})
|
|
45
|
+
if has_index?(attr) && indexes[attr][:kind] != :single
|
|
46
|
+
raise "Cannot reuse index attr #{attr}"
|
|
49
47
|
end
|
|
50
48
|
|
|
51
49
|
super
|
|
52
50
|
|
|
53
51
|
case options[:index]
|
|
54
52
|
when nil then
|
|
55
|
-
when Hash then index(
|
|
56
|
-
when Symbol then index(
|
|
57
|
-
when true then index(
|
|
58
|
-
when false then remove_index(
|
|
53
|
+
when Hash then index(attr, options[:index])
|
|
54
|
+
when Symbol then index(attr, options[:index] => true)
|
|
55
|
+
when true then index(attr)
|
|
56
|
+
when false then remove_index(attr)
|
|
59
57
|
end
|
|
60
58
|
end
|
|
61
59
|
|
|
62
|
-
def remove_field(name)
|
|
63
|
-
remove_index(name) if fields[name.to_sym][:index]
|
|
64
|
-
super
|
|
65
|
-
end
|
|
66
|
-
|
|
67
60
|
def perform_create_index(index_name, options={})
|
|
68
61
|
index_name = index_name.to_sym
|
|
69
62
|
index_args = self.indexes[index_name]
|
|
@@ -2,10 +2,9 @@ module NoBrainer::Document::InjectionLayer
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
module ClassMethods
|
|
5
|
-
def inject_in_layer(name,
|
|
5
|
+
def inject_in_layer(name, &block)
|
|
6
6
|
mod = class_eval "module NoBrainerLayer; module #{name.to_s.camelize}; self; end; end"
|
|
7
|
-
mod.
|
|
8
|
-
mod.module_exec(&block) if block
|
|
7
|
+
mod.module_exec(&block)
|
|
9
8
|
include mod
|
|
10
9
|
end
|
|
11
10
|
end
|
|
@@ -6,10 +6,9 @@ module NoBrainer::Document::Persistance
|
|
|
6
6
|
define_model_callbacks :create, :update, :save, :destroy, :terminator => 'false'
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
def initialize(attrs={}, options={})
|
|
11
|
-
super
|
|
9
|
+
def _initialize(attrs={}, options={})
|
|
12
10
|
@new_record = !options[:from_db]
|
|
11
|
+
super
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
def new_record?
|
|
@@ -25,56 +24,43 @@ module NoBrainer::Document::Persistance
|
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
def reload(options={})
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@attributes = {}
|
|
32
|
-
self.id = id
|
|
33
|
-
end
|
|
34
|
-
assign_attributes(selector.raw.first!, :pristine => true, :from_db => true)
|
|
27
|
+
attrs = selector.raw.first!
|
|
28
|
+
instance_variables.each { |ivar| remove_instance_variable(ivar) } unless options[:keep_ivars]
|
|
29
|
+
initialize(attrs, :pristine => true, :from_db => true)
|
|
35
30
|
self
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
def _create(options={})
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
self.id ||= keys.first
|
|
45
|
-
@new_record = false
|
|
46
|
-
true
|
|
47
|
-
end
|
|
48
|
-
end
|
|
34
|
+
return false if options[:validate] && !valid?
|
|
35
|
+
keys = self.class.insert_all(@_attributes)
|
|
36
|
+
self.id ||= keys.first
|
|
37
|
+
@new_record = false
|
|
38
|
+
true
|
|
49
39
|
end
|
|
50
40
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
if options[:validate] && !valid?
|
|
54
|
-
false
|
|
55
|
-
else
|
|
56
|
-
selector.update_all(&block)
|
|
57
|
-
true
|
|
58
|
-
end
|
|
59
|
-
end
|
|
41
|
+
def _update(attrs)
|
|
42
|
+
selector.update_all(attrs)
|
|
60
43
|
end
|
|
61
44
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
45
|
+
def _update_only_changed_attrs(options={})
|
|
46
|
+
return false if options[:validate] && !valid?
|
|
47
|
+
|
|
48
|
+
# We won't be using the `changes` values, because they went through
|
|
49
|
+
# read_attribute(), and we want the raw values.
|
|
50
|
+
attrs = Hash[self.changed.map do |k|
|
|
51
|
+
attr = @_attributes[k]
|
|
52
|
+
# If we have a hash to save, we need to specify r.literal(),
|
|
53
|
+
# otherwise, the hash would just get merged with the existing one.
|
|
54
|
+
attr = RethinkDB::RQL.new.literal(attr) if attr.is_a?(Hash)
|
|
55
|
+
[k, attr]
|
|
56
|
+
end]
|
|
57
|
+
_update(attrs) if attrs.present?
|
|
58
|
+
true
|
|
71
59
|
end
|
|
72
60
|
|
|
73
61
|
def save(options={})
|
|
74
62
|
options = options.reverse_merge(:validate => true)
|
|
75
|
-
|
|
76
|
-
new_record? ? _create(options) : replace(options) { attributes }
|
|
77
|
-
end
|
|
63
|
+
new_record? ? _create(options) : _update_only_changed_attrs(options)
|
|
78
64
|
end
|
|
79
65
|
|
|
80
66
|
def save!(*args)
|
|
@@ -95,12 +81,12 @@ module NoBrainer::Document::Persistance
|
|
|
95
81
|
selector.delete_all
|
|
96
82
|
@destroyed = true
|
|
97
83
|
end
|
|
98
|
-
|
|
84
|
+
@_attributes.freeze
|
|
99
85
|
true
|
|
100
86
|
end
|
|
101
87
|
|
|
102
88
|
def destroy
|
|
103
|
-
|
|
89
|
+
delete
|
|
104
90
|
end
|
|
105
91
|
|
|
106
92
|
module ClassMethods
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module NoBrainer::Document::Readonly
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def _field(attr, options={})
|
|
6
|
+
super
|
|
7
|
+
inject_in_layer :readonly do
|
|
8
|
+
if options[:readonly]
|
|
9
|
+
define_method("#{attr}=") do |value|
|
|
10
|
+
raise NoBrainer::Error::ReadonlyField.new("#{attr} is readonly") unless new_record?
|
|
11
|
+
super(value)
|
|
12
|
+
end
|
|
13
|
+
else
|
|
14
|
+
remove_method("#{attr}=") if method_defined?("#{attr}=")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -2,17 +2,17 @@ module NoBrainer::Document::Timestamps
|
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
4
|
included do
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
field :created_at, :type => Time
|
|
6
|
+
field :updated_at, :type => Time
|
|
7
|
+
end
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
def _create(options={})
|
|
10
|
+
self.created_at = self.updated_at = Time.now
|
|
11
|
+
super
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
self.remove_field :updated_at
|
|
16
|
-
end
|
|
14
|
+
def _update(attrs)
|
|
15
|
+
self.updated_at = Time.now
|
|
16
|
+
super(attrs.merge('updated_at' => @_attributes['updated_at']))
|
|
17
17
|
end
|
|
18
18
|
end
|