nobrainer 0.34.1 → 0.41.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/CHANGELOG.md +28 -1
- data/lib/no_brainer/criteria/where.rb +8 -0
- data/lib/no_brainer/document/association/eager_loader.rb +6 -2
- data/lib/no_brainer/document/store.rb +304 -0
- data/lib/no_brainer/document/types/array.rb +90 -0
- data/lib/no_brainer/document/types.rb +7 -2
- data/lib/no_brainer/document/validation/not_null.rb +1 -1
- data/lib/no_brainer/document/validation/uniqueness.rb +1 -1
- data/lib/no_brainer/document.rb +1 -1
- data/lib/no_brainer/error.rb +1 -1
- data/lib/no_brainer/symbol_decoration.rb +1 -1
- data/lib/nobrainer.rb +4 -0
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 477e66e4e3380775847ce79e706f50a04134e7e87002b31aa0ca33c9e164d9d7
|
4
|
+
data.tar.gz: b303bbb3dc6a45df84f39edd2cce6d26af27000939c56be05e8f036c0ad11e76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1f883d15467994b72aabddbb61adcadd7095a5220f89ad1b4d8e58cf6927cfb7a99e0e1443fc24d7edf37bf4a24c24e95d0bc99c47d39281b0e56c73ee549b0
|
7
|
+
data.tar.gz: '04592d9c64732b25089dccd897ad451cd2f71b12a96a93532604768c9c40c1757e6f2b528792976f8be054e376d0a3a4541ca3a3aa7de60014a7e88d97e10453'
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
|
10
|
+
## [0.41.0] - 2021-10-17
|
11
|
+
### Added
|
12
|
+
- ActiveRecord `store_accessor` helper method
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
- gemspec dependencies on activemodel and activesupport
|
16
|
+
|
17
|
+
## [0.40.0] - 2021-10-16
|
18
|
+
### Fixed
|
19
|
+
- Ruby 3 compatibility
|
20
|
+
- Test Ruby 3 + Rails 7 alpha2 on Travis CI
|
21
|
+
|
22
|
+
## [0.36.0] - 2021-08-08
|
23
|
+
### Added
|
24
|
+
- Array and TypedArray types for validation and serialization
|
25
|
+
|
26
|
+
## [0.35.0] - 2021-08-08
|
27
|
+
### Added
|
28
|
+
- Dockerfile, docker-compose and Earthfile
|
29
|
+
- Test Ruby 3 + Rails 6 on Travis CI
|
30
|
+
- Implements the ReQL `during` command
|
31
|
+
|
9
32
|
## [0.34.1] - 2021-02-18
|
10
33
|
### Fixed
|
11
34
|
- Defining attributes at class level (Rails 6.1 compatibity)
|
@@ -93,7 +116,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
93
116
|
- Locks: bug fix: allow small timeouts in lock()
|
94
117
|
- Fix reentrant lock counter on steals
|
95
118
|
|
96
|
-
[Unreleased]: https://github.com/nobrainerorm/nobrainer/compare/v0.
|
119
|
+
[Unreleased]: https://github.com/nobrainerorm/nobrainer/compare/v0.41.0...HEAD
|
120
|
+
[0.41.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.40.0...v0.41.0
|
121
|
+
[0.40.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.36.0...v0.40.0
|
122
|
+
[0.36.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.35.0...v0.36.0
|
123
|
+
[0.35.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.34.1...v0.35.0
|
97
124
|
[0.34.1]: https://github.com/nobrainerorm/nobrainer/compare/v0.34.0...v0.34.1
|
98
125
|
[0.34.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.33.0...v0.34.0
|
99
126
|
[0.33.0]: https://github.com/nobrainerorm/nobrainer/compare/v0.32.0...v0.33.0
|
@@ -104,6 +104,7 @@ module NoBrainer::Criteria::Where
|
|
104
104
|
when :between then [key_modifier, op, (cast_value(value.min)..cast_value(value.max))]
|
105
105
|
when :include then ensure_scalar_for(op); [:any, :eq, cast_value(value)]
|
106
106
|
when :defined, :undefined then ensure_scalar_for(op); [key_modifier, op, cast_value(value)]
|
107
|
+
when :during then [key_modifier, op, [cast_value(value.first), cast_value(value.last)]]
|
107
108
|
else [key_modifier, op, cast_value(value)]
|
108
109
|
end
|
109
110
|
BinaryOperator.new(new_key_path, new_key_modifier, new_op, new_value, model, true)
|
@@ -132,6 +133,7 @@ module NoBrainer::Criteria::Where
|
|
132
133
|
when :between then (lvalue >= value.min) & (lvalue <= value.max)
|
133
134
|
when :in then RethinkDB::RQL.new.expr(value).contains(lvalue)
|
134
135
|
when :intersects then lvalue.intersects(value.to_rql)
|
136
|
+
when :during then lvalue.during(value.first, value.last)
|
135
137
|
when :near
|
136
138
|
# XXX options[:max_results] is not used, seems to be a workaround of rethinkdb index implementation.
|
137
139
|
circle = value[:circle]
|
@@ -244,6 +246,7 @@ module NoBrainer::Criteria::Where
|
|
244
246
|
when Symbol::Decoration
|
245
247
|
case clause.args.size
|
246
248
|
when 1 then parse_clause_stub(clause, clause.args.first, options)
|
249
|
+
when 2 then parse_clause_stub(clause, clause.args, options)
|
247
250
|
else raise "Invalid argument: #{clause}"
|
248
251
|
end
|
249
252
|
else raise "Invalid clause: #{clause}"
|
@@ -263,6 +266,11 @@ module NoBrainer::Criteria::Where
|
|
263
266
|
else instantiate_binary_op(key.to_sym, :eq, value, options)
|
264
267
|
end
|
265
268
|
when Symbol::Decoration then
|
269
|
+
# The :eq operator can have only one arg
|
270
|
+
if key.decorator == :eq && value.is_a?(Array) && value.size > 1
|
271
|
+
raise "Invalid key: #{key}"
|
272
|
+
end
|
273
|
+
|
266
274
|
case key.decorator
|
267
275
|
when :any, :all, :not then instantiate_binary_op(key, :eq, value, options)
|
268
276
|
when :gte then instantiate_binary_op(key.symbol, :ge, value, options)
|
@@ -33,8 +33,12 @@ module NoBrainer::Document::Association::EagerLoader
|
|
33
33
|
def eager_load_association(docs, association_name, criteria=nil)
|
34
34
|
docs = docs.compact
|
35
35
|
return [] if docs.empty?
|
36
|
-
|
37
|
-
|
36
|
+
|
37
|
+
meta = docs.first.class.association_metadata
|
38
|
+
root_meta = docs.first.root_class.association_metadata
|
39
|
+
association = meta[association_name.to_sym] || root_meta[association_name.to_sym] ||
|
40
|
+
meta[association_name.to_s.singularize.to_sym] || root_meta[association_name.to_s.singularize.to_sym]
|
41
|
+
|
38
42
|
raise "Unknown association #{association_name}" unless association
|
39
43
|
association.eager_load(docs, criteria)
|
40
44
|
end
|
@@ -0,0 +1,304 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
4
|
+
|
5
|
+
module NoBrainer
|
6
|
+
module Document
|
7
|
+
# Store gives you a way for storing hashes in a single field with accessors
|
8
|
+
# to the Hash keys.
|
9
|
+
# It is a portage of the ActiveRecord::Store which make gems using it
|
10
|
+
# compatible with NoBrainer.
|
11
|
+
#
|
12
|
+
# You can then declare accessors to this store that are then accessible just
|
13
|
+
# like any other attribute of the model. This is very helpful for easily
|
14
|
+
# exposing store keys to a form or elsewhere that's already built around
|
15
|
+
# just accessing attributes on the model.
|
16
|
+
#
|
17
|
+
# Every accessor comes with dirty tracking methods (+key_changed?+,
|
18
|
+
# +key_was+ and +key_change+).
|
19
|
+
#
|
20
|
+
# You can set custom coder to encode/decode your serialized attributes
|
21
|
+
# to/from different formats.
|
22
|
+
# JSON, YAML, Marshal are supported out of the box.
|
23
|
+
# Generally it can be any wrapper that provides +load+ and +dump+.
|
24
|
+
#
|
25
|
+
# NOTE: The {.store}[rdoc-ref:rdoc-ref:ClassMethods#store] method is here
|
26
|
+
# for compatibility reason, but you should use
|
27
|
+
# {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead
|
28
|
+
# to generate the accessor methods.
|
29
|
+
# Be aware that these columns use a string keyed hash and do not allow
|
30
|
+
# access using a symbol.
|
31
|
+
#
|
32
|
+
# NOTE: The default validations with the exception of +uniqueness+ will work.
|
33
|
+
#
|
34
|
+
# Examples:
|
35
|
+
#
|
36
|
+
# class User
|
37
|
+
# include NoBrainer::Document
|
38
|
+
#
|
39
|
+
# store :settings, accessors: [ :color, :homepage ], coder: JSON
|
40
|
+
# store :parent, accessors: [ :name ], coder: JSON, prefix: true
|
41
|
+
# store :spouse, accessors: [ :name ], coder: JSON, prefix: :partner
|
42
|
+
# store :settings, accessors: [ :two_factor_auth ], suffix: true
|
43
|
+
# store :settings, accessors: [ :login_retry ], suffix: :config
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# u = User.new(color: 'black', homepage: '37signals.com',
|
47
|
+
# parent_name: 'Mary', partner_name: 'Lily')
|
48
|
+
# u.color # Accessor stored attribute
|
49
|
+
# u.parent_name # Accessor stored attribute with prefix
|
50
|
+
# u.partner_name # Accessor stored attribute with custom prefix
|
51
|
+
# u.two_factor_auth_settings # Accessor stored attribute with suffix
|
52
|
+
# u.login_retry_config # Accessor stored attribute with custom suffix
|
53
|
+
# u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
|
54
|
+
#
|
55
|
+
# # There is no difference between strings and symbols for accessing
|
56
|
+
# # custom attributes
|
57
|
+
# u.settings[:country] # => 'Denmark'
|
58
|
+
# u.settings['country'] # => 'Denmark'
|
59
|
+
#
|
60
|
+
# # Dirty tracking
|
61
|
+
# u.color = 'green'
|
62
|
+
# u.color_changed? # => true
|
63
|
+
# u.color_was # => 'black'
|
64
|
+
# u.color_change # => ['black', 'red']
|
65
|
+
#
|
66
|
+
# # Add additional accessors to an existing store through store_accessor
|
67
|
+
# class SuperUser < User
|
68
|
+
# store_accessor :settings, :privileges, :servants
|
69
|
+
# store_accessor :parent, :birthday, prefix: true
|
70
|
+
# store_accessor :settings, :secret_question, suffix: :config
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# The stored attribute names can be retrieved using
|
74
|
+
# {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes].
|
75
|
+
#
|
76
|
+
# User.stored_attributes[:settings]
|
77
|
+
# #=> [:color, :homepage, :two_factor_auth, :login_retry]
|
78
|
+
#
|
79
|
+
# == Overwriting default accessors
|
80
|
+
#
|
81
|
+
# All stored values are automatically available through accessors on
|
82
|
+
# the NoBrainer Document object, but sometimes you want to specialize
|
83
|
+
# this behavior. This can be done by overwriting the default accessors
|
84
|
+
# (using the same name as the attribute) and calling <tt>super</tt>
|
85
|
+
# to actually change things.
|
86
|
+
#
|
87
|
+
# class Song
|
88
|
+
# include NoBrainer::Document
|
89
|
+
#
|
90
|
+
# # Uses a stored integer to hold the volume adjustment of the song
|
91
|
+
# store :settings, accessors: [:volume_adjustment]
|
92
|
+
#
|
93
|
+
# def volume_adjustment=(decibels)
|
94
|
+
# super(decibels.to_i)
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# def volume_adjustment
|
98
|
+
# super.to_i
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
module Store
|
102
|
+
extend ActiveSupport::Concern
|
103
|
+
|
104
|
+
included do
|
105
|
+
class << self
|
106
|
+
attr_accessor :local_stored_attributes
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
module ClassMethods
|
111
|
+
def store(store_attribute, options = {})
|
112
|
+
store_accessor(store_attribute, options[:accessors], **options.slice(:prefix, :suffix)) if options.has_key? :accessors
|
113
|
+
end
|
114
|
+
|
115
|
+
def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
|
116
|
+
keys = keys.flatten
|
117
|
+
|
118
|
+
accessor_prefix =
|
119
|
+
case prefix
|
120
|
+
when String, Symbol
|
121
|
+
"#{prefix}_"
|
122
|
+
when TrueClass
|
123
|
+
"#{store_attribute}_"
|
124
|
+
else
|
125
|
+
""
|
126
|
+
end
|
127
|
+
accessor_suffix =
|
128
|
+
case suffix
|
129
|
+
when String, Symbol
|
130
|
+
"_#{suffix}"
|
131
|
+
when TrueClass
|
132
|
+
"_#{store_attribute}"
|
133
|
+
else
|
134
|
+
""
|
135
|
+
end
|
136
|
+
|
137
|
+
field store_attribute, type: Hash, default: {} unless has_field?(store_attribute)
|
138
|
+
|
139
|
+
define_method("#{store_attribute}=") do |value|
|
140
|
+
super(value) if value.is_a?(Hash) || value.nil?
|
141
|
+
end
|
142
|
+
|
143
|
+
_store_accessors_module.module_eval do
|
144
|
+
keys.each do |key|
|
145
|
+
accessor_key = "#{accessor_prefix}#{key}#{accessor_suffix}"
|
146
|
+
|
147
|
+
define_method("#{accessor_key}=") do |value|
|
148
|
+
write_store_attribute(store_attribute, key, value)
|
149
|
+
end
|
150
|
+
|
151
|
+
define_method(accessor_key) do
|
152
|
+
read_store_attribute(store_attribute, key)
|
153
|
+
end
|
154
|
+
|
155
|
+
define_method("#{accessor_key}_changed?") do
|
156
|
+
return false unless __send__("#{store_attribute}_changed?")
|
157
|
+
prev_store, new_store = changes[store_attribute]
|
158
|
+
if NoBrainer.rails4?
|
159
|
+
(prev_store && prev_store[key.to_s]) != (new_store && new_store[key.to_s])
|
160
|
+
else
|
161
|
+
(prev_store && prev_store.dig(key)) != (new_store && new_store.dig(key))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
define_method("#{accessor_key}_change") do
|
166
|
+
return unless __send__("#{store_attribute}_changed?")
|
167
|
+
prev_store, new_store = changes[store_attribute]
|
168
|
+
if NoBrainer.rails4?
|
169
|
+
[(prev_store && prev_store[key.to_s]), (new_store && new_store[key.to_s])]
|
170
|
+
else
|
171
|
+
[(prev_store && prev_store.dig(key)), (new_store && new_store.dig(key))]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
define_method("#{accessor_key}_was") do
|
176
|
+
return unless __send__("#{store_attribute}_changed?")
|
177
|
+
prev_store, _new_store = changes[store_attribute]
|
178
|
+
if NoBrainer.rails4?
|
179
|
+
(prev_store && prev_store[key.to_s])
|
180
|
+
else
|
181
|
+
(prev_store && prev_store.dig(key))
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# NoBrainer doesn't have `attribute_will_change!` so those methods
|
186
|
+
# can't be implemented yet.
|
187
|
+
# See https://github.com/NoBrainerORM/nobrainer/pull/190
|
188
|
+
#
|
189
|
+
# define_method("saved_change_to_#{accessor_key}?") do
|
190
|
+
# return false unless __send__("saved_change_to_#{store_attribute}?")
|
191
|
+
# prev_store, new_store = __send__("saved_change_to_#{store_attribute}")
|
192
|
+
# prev_store&.dig(key) != new_store&.dig(key)
|
193
|
+
# end
|
194
|
+
|
195
|
+
# define_method("saved_change_to_#{accessor_key}") do
|
196
|
+
# return unless __send__("saved_change_to_#{store_attribute}?")
|
197
|
+
# prev_store, new_store = __send__("saved_change_to_#{store_attribute}")
|
198
|
+
# [prev_store&.dig(key), new_store&.dig(key)]
|
199
|
+
# end
|
200
|
+
|
201
|
+
# define_method("#{accessor_key}_before_last_save") do
|
202
|
+
# return unless __send__("saved_change_to_#{store_attribute}?")
|
203
|
+
# prev_store, _new_store = __send__("saved_change_to_#{store_attribute}")
|
204
|
+
# prev_store&.dig(key)
|
205
|
+
# end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# assign new store attribute and create new hash to ensure that each class in the hierarchy
|
210
|
+
# has its own hash of stored attributes.
|
211
|
+
self.local_stored_attributes ||= {}
|
212
|
+
self.local_stored_attributes[store_attribute] ||= []
|
213
|
+
self.local_stored_attributes[store_attribute] |= keys
|
214
|
+
end
|
215
|
+
|
216
|
+
def _store_accessors_module # :nodoc:
|
217
|
+
@_store_accessors_module ||= begin
|
218
|
+
mod = Module.new
|
219
|
+
include mod
|
220
|
+
mod
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def stored_attributes
|
225
|
+
parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
|
226
|
+
if local_stored_attributes
|
227
|
+
parent.merge!(local_stored_attributes) { |k, a, b| a | b }
|
228
|
+
end
|
229
|
+
parent
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
def read_store_attribute(store_attribute, key) # :doc:
|
236
|
+
StringKeyedHashAccessor.read(self, store_attribute, key)
|
237
|
+
end
|
238
|
+
|
239
|
+
def write_store_attribute(store_attribute, key, value) # :doc:
|
240
|
+
StringKeyedHashAccessor.write(self, store_attribute, key, value)
|
241
|
+
end
|
242
|
+
|
243
|
+
class HashAccessor # :nodoc:
|
244
|
+
def self.read(object, attribute, key)
|
245
|
+
prepare(object, attribute)
|
246
|
+
object.public_send(attribute)[key]
|
247
|
+
end
|
248
|
+
|
249
|
+
def self.write(object, attribute, key, value)
|
250
|
+
prepare(object, attribute)
|
251
|
+
if value != read(object, attribute, key)
|
252
|
+
# "#{attribute}_will_change!" is not implemented in NoBrainer. See issue #190
|
253
|
+
# object.public_send :"#{attribute}_will_change!"
|
254
|
+
object.public_send(attribute)[key] = value
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.prepare(object, attribute)
|
259
|
+
object.public_send :"#{attribute}=", {} unless object.send(attribute)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class StringKeyedHashAccessor < HashAccessor # :nodoc:
|
264
|
+
def self.read(object, attribute, key)
|
265
|
+
super object, attribute, key.to_s
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.write(object, attribute, key, value)
|
269
|
+
super object, attribute, key.to_s, value
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class IndifferentCoder # :nodoc:
|
274
|
+
def initialize(attr_name, coder_or_class_name)
|
275
|
+
@coder =
|
276
|
+
if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
|
277
|
+
coder_or_class_name
|
278
|
+
else
|
279
|
+
NoBrainer::Document::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def dump(obj)
|
284
|
+
@coder.dump self.class.as_indifferent_hash(obj)
|
285
|
+
end
|
286
|
+
|
287
|
+
def load(yaml)
|
288
|
+
self.class.as_indifferent_hash(@coder.load(yaml || ""))
|
289
|
+
end
|
290
|
+
|
291
|
+
def self.as_indifferent_hash(obj)
|
292
|
+
case obj
|
293
|
+
when ActiveSupport::HashWithIndifferentAccess
|
294
|
+
obj
|
295
|
+
when Hash
|
296
|
+
obj.with_indifferent_access
|
297
|
+
else
|
298
|
+
ActiveSupport::HashWithIndifferentAccess.new
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
|
3
|
+
module NoBrainer
|
4
|
+
class Array < ::Array
|
5
|
+
# delegate cast to each array element
|
6
|
+
def self.nobrainer_cast_user_to_model(values)
|
7
|
+
::Array.wrap(values).map do |value|
|
8
|
+
if value.class.respond_to?(:nobrainer_cast_user_to_model)
|
9
|
+
value.class.nobrainer_cast_user_to_model(value)
|
10
|
+
else
|
11
|
+
value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# delegate cast to each array element
|
17
|
+
def self.nobrainer_cast_model_to_db(values)
|
18
|
+
::Array.wrap(values).map do |value|
|
19
|
+
if value.class.respond_to?(:nobrainer_cast_model_to_db)
|
20
|
+
value.class.nobrainer_cast_model_to_db(value)
|
21
|
+
else
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# delegate cast to each array element
|
28
|
+
def self.nobrainer_cast_db_to_model(values)
|
29
|
+
::Array.wrap(values).map do |value|
|
30
|
+
if value.class.respond_to?(:nobrainer_cast_db_to_model)
|
31
|
+
value.class.nobrainer_cast_db_to_model(method, value)
|
32
|
+
else
|
33
|
+
value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# convenience method to create a TypedArray
|
39
|
+
def self.of(object_type = nil, **options)
|
40
|
+
NoBrainer::TypedArray.of(object_type, **options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class TypedArray < Array
|
45
|
+
def self.of(object_type, allow_nil: false)
|
46
|
+
NoBrainer::Document::Types.load_type_extensions(object_type)
|
47
|
+
::Class.new(TypedArray) do
|
48
|
+
define_singleton_method(:object_type) { object_type }
|
49
|
+
define_singleton_method(:allow_nil?) { allow_nil }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.name
|
54
|
+
str = String.new "Array"
|
55
|
+
str += "(#{object_type.name})" if respond_to?(:object_type)
|
56
|
+
str
|
57
|
+
end
|
58
|
+
|
59
|
+
# delegate cast methods to object_type cast methods, if defined
|
60
|
+
def self.nobrainer_cast_user_to_model(values)
|
61
|
+
cast_type = object_type.respond_to?(:nobrainer_cast_user_to_model) && object_type
|
62
|
+
values = ::Array.wrap(values).map do |value|
|
63
|
+
value = cast_type.nobrainer_cast_user_to_model(value) if cast_type
|
64
|
+
unless (value.nil? && allow_nil?) || value.is_a?(object_type)
|
65
|
+
raise NoBrainer::Error::InvalidType, type: object_type.name, value: value
|
66
|
+
end
|
67
|
+
value
|
68
|
+
end
|
69
|
+
new(values)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.nobrainer_cast_model_to_db(values)
|
73
|
+
values = ::Array.wrap(values)
|
74
|
+
if object_type.respond_to?(:nobrainer_cast_model_to_db)
|
75
|
+
values.map { |value| object_type.nobrainer_cast_model_to_db(value) }
|
76
|
+
else
|
77
|
+
values
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.nobrainer_cast_db_to_model(values)
|
82
|
+
values = ::Array.wrap(values)
|
83
|
+
if object_type.respond_to?(:nobrainer_cast_db_to_model)
|
84
|
+
values.map { |value| object_type.nobrainer_cast_db_to_model(value) }
|
85
|
+
else
|
86
|
+
values
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -9,7 +9,7 @@ module NoBrainer::Document::Types
|
|
9
9
|
def add_type_errors
|
10
10
|
return unless @pending_type_errors
|
11
11
|
@pending_type_errors.each do |name, error|
|
12
|
-
errors.add(name, :invalid_type, error.error)
|
12
|
+
errors.add(name, :invalid_type, **error.error)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -62,6 +62,11 @@ module NoBrainer::Document::Types
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def field(attr, options={})
|
65
|
+
if (type = options[:type]).is_a?(::Array)
|
66
|
+
raise ArgumentError, "Expected Array type to have single element, got #{types.inspect}" unless type.length == 1
|
67
|
+
options[:type] = NoBrainer::TypedArray.of(type.first)
|
68
|
+
end
|
69
|
+
|
65
70
|
super
|
66
71
|
|
67
72
|
type = options[:type]
|
@@ -108,7 +113,7 @@ module NoBrainer::Document::Types
|
|
108
113
|
end
|
109
114
|
end
|
110
115
|
|
111
|
-
%w(binary boolean text geo enum).each do |type|
|
116
|
+
%w(array binary boolean text geo enum).each do |type|
|
112
117
|
require File.join(File.dirname(__FILE__), 'types', type)
|
113
118
|
const_set(type.camelize, NoBrainer.const_get(type.camelize))
|
114
119
|
end
|
@@ -9,7 +9,7 @@ module NoBrainer::Document::Validation::NotNull
|
|
9
9
|
|
10
10
|
class NotNullValidator < ActiveModel::EachValidator
|
11
11
|
def validate_each(doc, attr, value)
|
12
|
-
doc.errors.add(attr, :undefined, options) if value.nil?
|
12
|
+
doc.errors.add(attr, :undefined, **options) if value.nil?
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -74,7 +74,7 @@ module NoBrainer::Document::Validation::Uniqueness
|
|
74
74
|
criteria = self.model.unscoped.where(attr => value)
|
75
75
|
criteria = apply_scopes(criteria, doc)
|
76
76
|
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
77
|
-
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless criteria.empty?
|
77
|
+
doc.errors.add(attr, :taken, **options.except(:scope).merge(:value => value)) unless criteria.empty?
|
78
78
|
rescue NoBrainer::Error::InvalidType
|
79
79
|
# We can't run the uniqueness validator: where() won't accept bad types
|
80
80
|
# and we have some values that don't have the right type.
|
data/lib/no_brainer/document.rb
CHANGED
@@ -7,7 +7,7 @@ module NoBrainer::Document
|
|
7
7
|
autoload_and_include :Core, :TableConfig, :InjectionLayer, :Attributes, :Readonly,
|
8
8
|
:Persistance, :Callbacks, :Validation, :Types, :Dirty, :PrimaryKey,
|
9
9
|
:Association, :Serialization, :Criteria, :Polymorphic, :Index, :Aliases,
|
10
|
-
:MissingAttributes, :LazyFetch, :AtomicOps, :VirtualAttributes
|
10
|
+
:MissingAttributes, :LazyFetch, :AtomicOps, :VirtualAttributes, :Store
|
11
11
|
|
12
12
|
autoload :DynamicAttributes, :Timestamps
|
13
13
|
|
data/lib/no_brainer/error.rb
CHANGED
@@ -51,7 +51,7 @@ module NoBrainer::Error
|
|
51
51
|
value = self.value
|
52
52
|
mock = model.allocate
|
53
53
|
mock.singleton_class.send(:define_method, :read_attribute_for_validation) { |_| value }
|
54
|
-
mock.errors.add(attr_name, :invalid_type, error)
|
54
|
+
mock.errors.add(attr_name, :invalid_type, **error)
|
55
55
|
mock.errors.full_messages.first
|
56
56
|
end
|
57
57
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module NoBrainer::SymbolDecoration
|
2
|
-
NON_CHAINABLE_OPERATORS = %w(in eq gt ge gte lt le lte defined undefined near intersects include).map(&:to_sym)
|
2
|
+
NON_CHAINABLE_OPERATORS = %w(in eq gt ge gte lt le lte defined undefined near intersects include during).map(&:to_sym)
|
3
3
|
CHAINABLE_OPERATORS = %w(not any all).map(&:to_sym)
|
4
4
|
OPERATORS = CHAINABLE_OPERATORS + NON_CHAINABLE_OPERATORS
|
5
5
|
|
data/lib/nobrainer.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nobrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.41.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Viennot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -17,6 +17,9 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 4.1.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -24,6 +27,9 @@ dependencies:
|
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 4.1.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: activesupport
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,6 +37,9 @@ dependencies:
|
|
31
37
|
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
39
|
version: 4.1.0
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '8'
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -38,6 +47,9 @@ dependencies:
|
|
38
47
|
- - ">="
|
39
48
|
- !ruby/object:Gem::Version
|
40
49
|
version: 4.1.0
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '8'
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: middleware
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,6 +71,9 @@ dependencies:
|
|
59
71
|
- - ">="
|
60
72
|
- !ruby/object:Gem::Version
|
61
73
|
version: 2.3.0
|
74
|
+
- - "<"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.5'
|
62
77
|
type: :runtime
|
63
78
|
prerelease: false
|
64
79
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -66,6 +81,9 @@ dependencies:
|
|
66
81
|
- - ">="
|
67
82
|
- !ruby/object:Gem::Version
|
68
83
|
version: 2.3.0
|
84
|
+
- - "<"
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '2.5'
|
69
87
|
- !ruby/object:Gem::Dependency
|
70
88
|
name: symbol_decoration
|
71
89
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,10 +168,12 @@ files:
|
|
150
168
|
- lib/no_brainer/document/primary_key/generator.rb
|
151
169
|
- lib/no_brainer/document/readonly.rb
|
152
170
|
- lib/no_brainer/document/serialization.rb
|
171
|
+
- lib/no_brainer/document/store.rb
|
153
172
|
- lib/no_brainer/document/table_config.rb
|
154
173
|
- lib/no_brainer/document/table_config/synchronizer.rb
|
155
174
|
- lib/no_brainer/document/timestamps.rb
|
156
175
|
- lib/no_brainer/document/types.rb
|
176
|
+
- lib/no_brainer/document/types/array.rb
|
157
177
|
- lib/no_brainer/document/types/binary.rb
|
158
178
|
- lib/no_brainer/document/types/boolean.rb
|
159
179
|
- lib/no_brainer/document/types/date.rb
|
@@ -221,7 +241,7 @@ files:
|
|
221
241
|
- lib/rails/generators/templates/nobrainer.rb
|
222
242
|
homepage: http://nobrainer.io
|
223
243
|
licenses:
|
224
|
-
-
|
244
|
+
- LGPL-3.0-only
|
225
245
|
metadata: {}
|
226
246
|
post_install_message:
|
227
247
|
rdoc_options: []
|
@@ -238,7 +258,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
258
|
- !ruby/object:Gem::Version
|
239
259
|
version: '0'
|
240
260
|
requirements: []
|
241
|
-
rubygems_version: 3.1.
|
261
|
+
rubygems_version: 3.1.6
|
242
262
|
signing_key:
|
243
263
|
specification_version: 4
|
244
264
|
summary: A Ruby ORM for RethinkDB
|