couch_potato 1.6.4 → 1.9.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 +5 -5
- data/.github/workflows/ruby.yml +38 -0
- data/.gitignore +3 -0
- data/CHANGES.md +193 -122
- data/Gemfile +4 -0
- data/README.md +61 -85
- data/Rakefile +11 -10
- data/couch_potato-rspec.gemspec +3 -2
- data/couch_potato.gemspec +9 -7
- data/gemfiles/active_support_5_0 +7 -0
- data/gemfiles/active_support_5_1 +7 -0
- data/gemfiles/active_support_5_2 +7 -0
- data/gemfiles/active_support_6_0 +7 -0
- data/gemfiles/active_support_6_1 +7 -0
- data/lib/couch_potato/database.rb +168 -71
- data/lib/couch_potato/persistence/dirty_attributes.rb +3 -21
- data/lib/couch_potato/persistence/magic_timestamps.rb +3 -3
- data/lib/couch_potato/persistence/properties.rb +15 -10
- data/lib/couch_potato/persistence/revisions.rb +14 -0
- data/lib/couch_potato/persistence/simple_property.rb +0 -4
- data/lib/couch_potato/persistence/type_caster.rb +11 -6
- data/lib/couch_potato/persistence.rb +5 -3
- data/lib/couch_potato/railtie.rb +7 -12
- data/lib/couch_potato/validation.rb +8 -0
- data/lib/couch_potato/version.rb +2 -2
- data/lib/couch_potato/view/base_view_spec.rb +8 -32
- data/lib/couch_potato/view/custom_views.rb +4 -3
- data/lib/couch_potato/view/flex_view_spec.rb +121 -0
- data/lib/couch_potato/view/view_parameters.rb +34 -0
- data/lib/couch_potato.rb +37 -16
- data/spec/callbacks_spec.rb +45 -19
- data/spec/conflict_handling_spec.rb +1 -2
- data/spec/property_spec.rb +12 -3
- data/spec/railtie_spec.rb +17 -1
- data/spec/revisions_spec.rb +25 -0
- data/spec/spec_helper.rb +4 -3
- data/spec/unit/active_model_compliance_spec.rb +7 -3
- data/spec/unit/attributes_spec.rb +54 -1
- data/spec/unit/caching_spec.rb +105 -0
- data/spec/unit/couch_potato_spec.rb +70 -5
- data/spec/unit/create_spec.rb +5 -4
- data/spec/unit/database_spec.rb +235 -135
- data/spec/unit/dirty_attributes_spec.rb +5 -26
- data/spec/unit/flex_view_spec_spec.rb +17 -0
- data/spec/unit/model_view_spec_spec.rb +1 -1
- data/spec/unit/rspec_stub_db_spec.rb +31 -0
- data/spec/unit/validation_spec.rb +42 -2
- data/spec/views_spec.rb +214 -103
- data/vendor/pouchdb-collate/LICENSE +202 -0
- data/vendor/pouchdb-collate/pouchdb-collate.js +430 -0
- metadata +46 -33
- data/.ruby-version +0 -1
- data/.travis.yml +0 -20
- data/gemfiles/active_support_4_0 +0 -11
- data/gemfiles/active_support_4_1 +0 -11
- data/gemfiles/active_support_4_2 +0 -11
- data/lib/couch_potato/persistence/deep_dirty_attributes.rb +0 -180
- data/spec/unit/deep_dirty_attributes_spec.rb +0 -434
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module CouchPotato
|
|
2
4
|
class Database
|
|
3
|
-
|
|
4
5
|
class ValidationsFailedError < ::StandardError; end
|
|
6
|
+
# Pass in a cache to enable caching #load calls.
|
|
7
|
+
# A cache needs to respond to #[], #[]= and #clear (just use a Hash).
|
|
8
|
+
attr_accessor :cache
|
|
9
|
+
cattr_accessor :default_batch_size
|
|
10
|
+
self.default_batch_size = 500
|
|
11
|
+
attr_reader :name # the (unresolved) name of the database unless this is the default database
|
|
5
12
|
|
|
6
|
-
def initialize(couchrest_database)
|
|
13
|
+
def initialize(couchrest_database, name: nil)
|
|
14
|
+
@name = name
|
|
7
15
|
@couchrest_database = couchrest_database
|
|
8
16
|
end
|
|
9
17
|
|
|
@@ -37,30 +45,40 @@ module CouchPotato
|
|
|
37
45
|
#
|
|
38
46
|
# db.view(User.all(keys: [1, 2, 3]))
|
|
39
47
|
def view(spec)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
id = view_cache_id(spec)
|
|
49
|
+
cached = cache && id.is_a?(String) && cache[id]
|
|
50
|
+
if cache
|
|
51
|
+
if cached
|
|
52
|
+
ActiveSupport::Notifications.instrument('couch_potato.view.cached') do
|
|
53
|
+
cached
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
cache[id] = view_without_caching(spec)
|
|
57
|
+
cache[id]
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
view_without_caching(spec)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Same as #view but instead of returning the results, it yields them
|
|
65
|
+
# to a given block in batches of the given size, making multiple
|
|
66
|
+
# requests with according skip/limit params sent to CouchDB.
|
|
67
|
+
def view_in_batches(spec, batch_size: default_batch_size)
|
|
68
|
+
batch = 0
|
|
69
|
+
loop do
|
|
70
|
+
spec.view_parameters = spec.view_parameters.merge({ skip: batch * batch_size, limit: batch_size })
|
|
71
|
+
results = view(spec)
|
|
72
|
+
yield results
|
|
73
|
+
break if results.size < batch_size
|
|
74
|
+
|
|
75
|
+
batch += 1
|
|
58
76
|
end
|
|
59
77
|
end
|
|
60
78
|
|
|
61
79
|
# returns the first result from a #view query or nil
|
|
62
80
|
def first(spec)
|
|
63
|
-
spec.view_parameters = spec.view_parameters.merge({:
|
|
81
|
+
spec.view_parameters = spec.view_parameters.merge({ limit: 1 })
|
|
64
82
|
view(spec).first
|
|
65
83
|
end
|
|
66
84
|
|
|
@@ -74,33 +92,35 @@ module CouchPotato
|
|
|
74
92
|
# * yield the object to be saved to the block and run if once before saving
|
|
75
93
|
# * on conflict: reload the document, run the block again and retry saving
|
|
76
94
|
def save_document(document, validate = true, retries = 0, &block)
|
|
95
|
+
cache&.clear
|
|
77
96
|
begin
|
|
78
|
-
block
|
|
97
|
+
block&.call document
|
|
79
98
|
save_document_without_conflict_handling(document, validate)
|
|
80
|
-
rescue CouchRest::Conflict
|
|
99
|
+
rescue CouchRest::Conflict
|
|
81
100
|
if block
|
|
82
101
|
handle_write_conflict document, validate, retries, &block
|
|
83
102
|
else
|
|
84
|
-
raise CouchPotato::Conflict
|
|
103
|
+
raise CouchPotato::Conflict
|
|
85
104
|
end
|
|
86
105
|
end
|
|
87
106
|
end
|
|
88
|
-
|
|
107
|
+
alias save save_document
|
|
89
108
|
|
|
90
109
|
# saves a document, raises a CouchPotato::Database::ValidationsFailedError on failure
|
|
91
110
|
def save_document!(document)
|
|
92
|
-
save_document(document) || raise(ValidationsFailedError
|
|
111
|
+
save_document(document) || raise(ValidationsFailedError, document.errors.full_messages)
|
|
93
112
|
end
|
|
94
|
-
|
|
113
|
+
alias save! save_document!
|
|
95
114
|
|
|
96
115
|
def destroy_document(document)
|
|
116
|
+
cache&.clear
|
|
97
117
|
begin
|
|
98
118
|
destroy_document_without_conflict_handling document
|
|
99
119
|
rescue CouchRest::Conflict
|
|
100
120
|
retry if document = document.reload
|
|
101
121
|
end
|
|
102
122
|
end
|
|
103
|
-
|
|
123
|
+
alias destroy destroy_document
|
|
104
124
|
|
|
105
125
|
# loads a document by its id(s)
|
|
106
126
|
# id - either a single id or an array of ids
|
|
@@ -108,28 +128,29 @@ module CouchPotato
|
|
|
108
128
|
# returns nil if the single document could not be found. when passing an array and some documents
|
|
109
129
|
# could not be found these are omitted from the returned array
|
|
110
130
|
def load_document(id)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
131
|
+
cached = cache && id.is_a?(String) && cache[id]
|
|
132
|
+
if cache
|
|
133
|
+
if cached
|
|
134
|
+
ActiveSupport::Notifications.instrument('couch_potato.load.cached') do
|
|
135
|
+
cached
|
|
136
|
+
end
|
|
116
137
|
else
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
instance
|
|
138
|
+
cache[id] = load_document_without_caching(id)
|
|
139
|
+
cache[id]
|
|
120
140
|
end
|
|
141
|
+
else
|
|
142
|
+
load_document_without_caching(id)
|
|
121
143
|
end
|
|
122
144
|
end
|
|
123
|
-
|
|
145
|
+
alias load load_document
|
|
124
146
|
|
|
125
147
|
# loads one or more documents by its id(s)
|
|
126
148
|
# behaves like #load except it raises a CouchPotato::NotFound if any of the documents could not be found
|
|
127
149
|
def load!(id)
|
|
128
150
|
doc = load(id)
|
|
129
|
-
if id.is_a?(Array)
|
|
130
|
-
missing_docs = id - doc.map(&:id)
|
|
131
|
-
end
|
|
151
|
+
missing_docs = id - doc.map(&:id) if id.is_a?(Array)
|
|
132
152
|
raise(CouchPotato::NotFound, missing_docs.try(:join, ', ')) if doc.nil? || missing_docs.try(:any?)
|
|
153
|
+
|
|
133
154
|
doc
|
|
134
155
|
end
|
|
135
156
|
|
|
@@ -138,17 +159,94 @@ module CouchPotato
|
|
|
138
159
|
end
|
|
139
160
|
|
|
140
161
|
# returns the underlying CouchRest::Database instance
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
attr_reader :couchrest_database
|
|
163
|
+
|
|
164
|
+
# returns a new database instance connected to the CouchDB database
|
|
165
|
+
# with the given name. the name is passed through the
|
|
166
|
+
# additional_databases configuration to resolve it to a database
|
|
167
|
+
# configured there.
|
|
168
|
+
# if the current database has a cache, the new database will receive
|
|
169
|
+
# a cleared copy of it.
|
|
170
|
+
def switch_to(database_name)
|
|
171
|
+
resolved_database_name = CouchPotato.resolve_database_name(database_name)
|
|
172
|
+
self
|
|
173
|
+
.class
|
|
174
|
+
.new(CouchPotato.couchrest_database_for_name(resolved_database_name), name: database_name)
|
|
175
|
+
.tap(©_clear_cache_proc)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# returns a new database instance connected to the default CouchDB database.
|
|
179
|
+
# if the current database has a cache, the new database will receive
|
|
180
|
+
# a cleared copy of it.
|
|
181
|
+
def switch_to_default
|
|
182
|
+
self
|
|
183
|
+
.class
|
|
184
|
+
.new(CouchPotato.couchrest_database)
|
|
185
|
+
.tap(©_clear_cache_proc)
|
|
143
186
|
end
|
|
144
187
|
|
|
145
188
|
private
|
|
146
189
|
|
|
190
|
+
def copy_clear_cache_proc
|
|
191
|
+
lambda { |db|
|
|
192
|
+
next unless cache
|
|
193
|
+
|
|
194
|
+
db.cache = cache.dup
|
|
195
|
+
db.cache.clear
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def view_without_caching(spec)
|
|
200
|
+
ActiveSupport::Notifications.instrument('couch_potato.view', name: "#{spec.design_document}/#{spec.view_name}") do
|
|
201
|
+
results = CouchPotato::View::ViewQuery.new(
|
|
202
|
+
couchrest_database,
|
|
203
|
+
spec.design_document,
|
|
204
|
+
{ spec.view_name => {
|
|
205
|
+
map: spec.map_function,
|
|
206
|
+
reduce: spec.reduce_function
|
|
207
|
+
} },
|
|
208
|
+
({ spec.list_name => spec.list_function } unless spec.list_name.nil?),
|
|
209
|
+
spec.lib,
|
|
210
|
+
spec.language
|
|
211
|
+
).query_view!(spec.view_parameters)
|
|
212
|
+
processed_results = spec.process_results results
|
|
213
|
+
if processed_results.respond_to?(:database=)
|
|
214
|
+
processed_results.database = self
|
|
215
|
+
elsif processed_results.respond_to?(:each)
|
|
216
|
+
processed_results.each do |document|
|
|
217
|
+
document.database = self if document.respond_to?(:database=)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
processed_results
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def load_document_without_caching(id)
|
|
225
|
+
raise "Can't load a document without an id (got nil)" if id.nil?
|
|
226
|
+
|
|
227
|
+
ActiveSupport::Notifications.instrument('couch_potato.load') do
|
|
228
|
+
if id.is_a?(Array)
|
|
229
|
+
bulk_load id
|
|
230
|
+
else
|
|
231
|
+
instance = couchrest_database.get(id)
|
|
232
|
+
instance.database = self if instance
|
|
233
|
+
instance
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def view_cache_id(spec)
|
|
239
|
+
spec.send(:klass).to_s + spec.view_name.to_s + spec.view_parameters.to_s
|
|
240
|
+
end
|
|
241
|
+
|
|
147
242
|
def handle_write_conflict(document, validate, retries, &block)
|
|
148
|
-
|
|
243
|
+
cache&.clear
|
|
149
244
|
if retries == 5
|
|
150
|
-
raise CouchPotato::Conflict
|
|
245
|
+
raise CouchPotato::Conflict
|
|
151
246
|
else
|
|
247
|
+
reloaded = document.reload
|
|
248
|
+
document.attributes = reloaded.attributes
|
|
249
|
+
document._rev = reloaded._rev
|
|
152
250
|
save_document document, validate, retries + 1, &block
|
|
153
251
|
end
|
|
154
252
|
end
|
|
@@ -172,10 +270,10 @@ module CouchPotato
|
|
|
172
270
|
|
|
173
271
|
def bulk_load(ids)
|
|
174
272
|
response = couchrest_database.bulk_load ids
|
|
175
|
-
docs = response['rows'].map{|row| row[
|
|
176
|
-
docs.each
|
|
273
|
+
docs = response['rows'].map { |row| row['doc'] }.compact
|
|
274
|
+
docs.each do |doc|
|
|
177
275
|
doc.database = self if doc.respond_to?(:database=)
|
|
178
|
-
|
|
276
|
+
end
|
|
179
277
|
end
|
|
180
278
|
|
|
181
279
|
def create_document(document, validate)
|
|
@@ -183,51 +281,50 @@ module CouchPotato
|
|
|
183
281
|
|
|
184
282
|
if validate
|
|
185
283
|
document.errors.clear
|
|
186
|
-
return false if
|
|
187
|
-
return false if
|
|
284
|
+
return false if document.run_callbacks(:validation_on_save) do
|
|
285
|
+
return false if document.run_callbacks(:validation_on_create) do
|
|
188
286
|
return false unless valid_document?(document)
|
|
189
|
-
end
|
|
190
|
-
end
|
|
287
|
+
end == false
|
|
288
|
+
end == false
|
|
191
289
|
end
|
|
192
290
|
|
|
193
|
-
return false if
|
|
194
|
-
return false if
|
|
291
|
+
return false if document.run_callbacks(:save) do
|
|
292
|
+
return false if document.run_callbacks(:create) do
|
|
195
293
|
res = couchrest_database.save_doc document.to_hash
|
|
196
294
|
document._rev = res['rev']
|
|
197
295
|
document._id = res['id']
|
|
198
|
-
end
|
|
199
|
-
end
|
|
296
|
+
end == false
|
|
297
|
+
end == false
|
|
298
|
+
|
|
200
299
|
true
|
|
201
300
|
end
|
|
202
301
|
|
|
203
302
|
def update_document(document, validate)
|
|
204
303
|
if validate
|
|
205
304
|
document.errors.clear
|
|
206
|
-
return false if
|
|
207
|
-
return false if
|
|
305
|
+
return false if document.run_callbacks(:validation_on_save) do
|
|
306
|
+
return false if document.run_callbacks(:validation_on_update) do
|
|
208
307
|
return false unless valid_document?(document)
|
|
209
|
-
end
|
|
210
|
-
end
|
|
308
|
+
end == false
|
|
309
|
+
end == false
|
|
211
310
|
end
|
|
212
311
|
|
|
213
|
-
return false if
|
|
214
|
-
return false if
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
end
|
|
312
|
+
return false if document.run_callbacks(:save) do
|
|
313
|
+
return false if document.run_callbacks(:update) do
|
|
314
|
+
res = couchrest_database.save_doc document.to_hash
|
|
315
|
+
document._rev = res['rev']
|
|
316
|
+
end == false
|
|
317
|
+
end == false
|
|
318
|
+
|
|
221
319
|
true
|
|
222
320
|
end
|
|
223
321
|
|
|
224
322
|
def valid_document?(document)
|
|
225
|
-
|
|
226
|
-
errors.instance_variable_set("@messages", errors.messages.dup) if errors.respond_to?(:messages)
|
|
323
|
+
original_errors_hash = document.errors.to_hash
|
|
227
324
|
document.valid?
|
|
228
|
-
|
|
325
|
+
original_errors_hash.each do |k, v|
|
|
229
326
|
if v.respond_to?(:each)
|
|
230
|
-
v.each {|message| document.errors.add(k, message)}
|
|
327
|
+
v.each { |message| document.errors.add(k, message) }
|
|
231
328
|
else
|
|
232
329
|
document.errors.add(k, v)
|
|
233
330
|
end
|
|
@@ -6,37 +6,19 @@ module CouchPotato
|
|
|
6
6
|
def self.included(base) #:nodoc:
|
|
7
7
|
base.send :include, ActiveModel::Dirty
|
|
8
8
|
base.class_eval do
|
|
9
|
-
after_save :
|
|
9
|
+
after_save :clear_changes_information
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
# returns true if a model has dirty attributes, i.e. their value has changed since the last save
|
|
14
|
-
def dirty?
|
|
15
|
-
changed? || @forced_dirty
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# marks a model as dirty
|
|
19
|
-
def is_dirty
|
|
20
|
-
@forced_dirty = true
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def reset_dirty_attributes
|
|
26
|
-
@previously_changed = changes
|
|
27
|
-
@changed_attributes.clear
|
|
28
|
-
@forced_dirty = nil
|
|
29
|
-
end
|
|
30
|
-
|
|
31
13
|
def clone_attribute(value)
|
|
32
|
-
if [
|
|
14
|
+
if [Integer, Symbol, TrueClass, FalseClass, NilClass, Float, BigDecimal].find{|klass| value.is_a?(klass)}
|
|
33
15
|
value
|
|
34
16
|
elsif [Hash, Array].include?(value.class)
|
|
35
17
|
#Deep clone
|
|
36
18
|
Marshal::load(Marshal::dump(value))
|
|
37
19
|
else
|
|
38
20
|
value.clone
|
|
39
|
-
|
|
21
|
+
end
|
|
40
22
|
end
|
|
41
23
|
end
|
|
42
24
|
end
|
|
@@ -9,13 +9,13 @@ module CouchPotato
|
|
|
9
9
|
|
|
10
10
|
before_create lambda {|model|
|
|
11
11
|
model.created_at ||= (Time.zone || Time).now
|
|
12
|
-
|
|
12
|
+
clear_attribute_changes [:created_at]
|
|
13
13
|
model.updated_at ||= (Time.zone || Time).now
|
|
14
|
-
|
|
14
|
+
clear_attribute_changes [:updated_at]
|
|
15
15
|
}
|
|
16
16
|
before_update lambda {|model|
|
|
17
17
|
model.updated_at = (Time.zone || Time).now
|
|
18
|
-
|
|
18
|
+
clear_attribute_changes [:updated_at]
|
|
19
19
|
}
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -85,18 +85,23 @@ module CouchPotato
|
|
|
85
85
|
# property :next_year, default: ->(book) { book.year + 1 }
|
|
86
86
|
# end
|
|
87
87
|
def property(name, options = {})
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
# ActiveModel::AttributeMethods.generated_attribute_methods
|
|
89
|
+
active_support_module = send(:generated_attribute_methods)
|
|
90
|
+
|
|
91
|
+
# Mimic ActiveModel::AttributeMethods.undefine_attribute_methods, but only for this
|
|
92
|
+
# property's accessor method
|
|
93
|
+
active_support_module.module_eval do
|
|
94
|
+
undef_method(name) if instance_methods.include?(name)
|
|
95
|
+
end
|
|
96
|
+
cache = send(:attribute_method_matchers_cache)
|
|
97
|
+
cache.delete(name)
|
|
98
|
+
|
|
99
|
+
define_attribute_method name
|
|
90
100
|
properties << SimpleProperty.new(self, name, options)
|
|
91
|
-
remove_attribute_accessors_from_activesupport_module
|
|
92
|
-
end
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
active_support_module
|
|
96
|
-
|
|
97
|
-
property_names.each do |name|
|
|
98
|
-
active_support_module.send :remove_method, name if active_support_module.instance_methods.include?(name)
|
|
99
|
-
end
|
|
102
|
+
# Remove the default ActiveModel::AttributeMethods accessor
|
|
103
|
+
if active_support_module.instance_methods.include?(name)
|
|
104
|
+
active_support_module.send(:remove_method, name)
|
|
100
105
|
end
|
|
101
106
|
end
|
|
102
107
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module CouchPotato
|
|
2
|
+
module Persistence
|
|
3
|
+
module Revisions
|
|
4
|
+
# returns all available revisions of a document, first to last.
|
|
5
|
+
# causes n+1 requests. do not use in production code.
|
|
6
|
+
def _revisions
|
|
7
|
+
with_revs = database.couchrest_database.get(id, revs: true, revs_info: true)._document
|
|
8
|
+
revs_info = with_revs[:_revs_info]
|
|
9
|
+
revs = revs_info.select {|info| info[:status] == 'available' }.map {|info| info[:rev] }
|
|
10
|
+
revs.reverse.map {|rev| database.couchrest_database.get(id, rev: rev) }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bigdecimal/util'
|
|
1
4
|
module CouchPotato
|
|
2
5
|
module Persistence
|
|
3
6
|
class TypeCaster #:nodoc:
|
|
4
|
-
NUMBER_REGEX = /-?\d*\.?\d
|
|
7
|
+
NUMBER_REGEX = /-?\d*\.?\d*/.freeze
|
|
5
8
|
|
|
6
9
|
def cast(value, type)
|
|
7
10
|
if type == :boolean
|
|
8
11
|
cast_boolean(value)
|
|
9
12
|
elsif type.instance_of?(Array)
|
|
10
13
|
nested_type = type.first
|
|
11
|
-
value
|
|
14
|
+
value&.map { |val| cast_native(val, nested_type) }
|
|
12
15
|
else
|
|
13
16
|
cast_native(value, type)
|
|
14
17
|
end
|
|
@@ -28,14 +31,17 @@ module CouchPotato
|
|
|
28
31
|
|
|
29
32
|
def cast_native(value, type)
|
|
30
33
|
if type && !value.is_a?(type)
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
|
|
35
|
+
if %w[Integer Bignum Fixnum].include?(type.to_s)
|
|
36
|
+
value.to_s.scan(NUMBER_REGEX).join.to_d.round unless value.blank?
|
|
33
37
|
elsif type == Float
|
|
34
38
|
value.to_s.scan(NUMBER_REGEX).join.to_f unless value.blank?
|
|
35
39
|
elsif type == BigDecimal
|
|
36
|
-
|
|
40
|
+
value.to_d unless value.blank?
|
|
37
41
|
elsif type == Hash
|
|
38
42
|
value.to_hash unless value.blank?
|
|
43
|
+
elsif type.ancestors.include?(CouchPotato::Persistence)
|
|
44
|
+
type.new value unless value.blank?
|
|
39
45
|
else
|
|
40
46
|
type.json_create value unless value.blank?
|
|
41
47
|
end
|
|
@@ -43,7 +49,6 @@ module CouchPotato
|
|
|
43
49
|
value
|
|
44
50
|
end
|
|
45
51
|
end
|
|
46
|
-
|
|
47
52
|
end
|
|
48
53
|
end
|
|
49
54
|
end
|
|
@@ -6,10 +6,10 @@ require File.dirname(__FILE__) + '/persistence/magic_timestamps'
|
|
|
6
6
|
require File.dirname(__FILE__) + '/persistence/callbacks'
|
|
7
7
|
require File.dirname(__FILE__) + '/persistence/json'
|
|
8
8
|
require File.dirname(__FILE__) + '/persistence/dirty_attributes'
|
|
9
|
-
require File.dirname(__FILE__) + '/persistence/deep_dirty_attributes'
|
|
10
9
|
require File.dirname(__FILE__) + '/persistence/ghost_attributes'
|
|
11
10
|
require File.dirname(__FILE__) + '/persistence/attachments'
|
|
12
11
|
require File.dirname(__FILE__) + '/persistence/type_caster'
|
|
12
|
+
require File.dirname(__FILE__) + '/persistence/revisions'
|
|
13
13
|
require File.dirname(__FILE__) + '/forbidden_attributes_protection'
|
|
14
14
|
require File.dirname(__FILE__) + '/view/custom_views'
|
|
15
15
|
require File.dirname(__FILE__) + '/view/lists'
|
|
@@ -20,9 +20,11 @@ module CouchPotato
|
|
|
20
20
|
module Persistence
|
|
21
21
|
|
|
22
22
|
def self.included(base) #:nodoc:
|
|
23
|
-
base.send :include, Properties, Callbacks, Json, CouchPotato::View::CustomViews,
|
|
23
|
+
base.send :include, Properties, Callbacks, Json, CouchPotato::View::CustomViews,
|
|
24
|
+
CouchPotato::View::Lists
|
|
24
25
|
base.send :include, DirtyAttributes, GhostAttributes, Attachments
|
|
25
|
-
base.send :include, MagicTimestamps, ActiveModelCompliance,
|
|
26
|
+
base.send :include, MagicTimestamps, ActiveModelCompliance,
|
|
27
|
+
ForbiddenAttributesProtection, Revisions
|
|
26
28
|
base.send :include, Validation
|
|
27
29
|
base.class_eval do
|
|
28
30
|
attr_accessor :_id, :_rev, :_deleted, :database
|
data/lib/couch_potato/railtie.rb
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'erb'
|
|
3
4
|
|
|
4
5
|
module CouchPotato
|
|
5
6
|
def self.rails_init
|
|
7
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../rails/reload_classes') if Rails.env.development?
|
|
6
8
|
path = Rails.root.join('config/couchdb.yml')
|
|
7
9
|
if File.exist?(path)
|
|
8
10
|
require 'yaml'
|
|
9
|
-
config = YAML
|
|
10
|
-
|
|
11
|
-
CouchPotato::Config.database_name = config
|
|
12
|
-
else
|
|
13
|
-
CouchPotato::Config.database_name = config['database']
|
|
14
|
-
CouchPotato::Config.split_design_documents_per_view = config['split_design_documents_per_view'] if config['split_design_documents_per_view']
|
|
15
|
-
CouchPotato::Config.digest_view_names = config['digest_view_names'] if config['digest_view_names']
|
|
16
|
-
CouchPotato::Config.default_language = config['default_language'] if config['default_language']
|
|
17
|
-
end
|
|
11
|
+
config = YAML.safe_load(ERB.new(File.read(path)).result, [Symbol], [], ['default'])[Rails.env]
|
|
12
|
+
CouchPotato.configure(config)
|
|
18
13
|
else
|
|
19
|
-
Rails.logger.warn
|
|
14
|
+
Rails.logger.warn 'Rails.root/config/couchdb.yml does not exist. Not configuring a database.'
|
|
20
15
|
end
|
|
21
16
|
end
|
|
22
17
|
|
|
23
18
|
if defined?(::Rails::Railtie)
|
|
24
19
|
class Railtie < ::Rails::Railtie
|
|
25
|
-
initializer 'couch_potato.load_config' do |
|
|
20
|
+
initializer 'couch_potato.load_config' do |_app|
|
|
26
21
|
CouchPotato.rails_init
|
|
27
22
|
end
|
|
28
23
|
end
|
|
@@ -3,9 +3,17 @@ require 'active_model/translation'
|
|
|
3
3
|
|
|
4
4
|
module CouchPotato
|
|
5
5
|
module Validation
|
|
6
|
+
module ValidationContext
|
|
7
|
+
def valid?(context = nil)
|
|
8
|
+
context ||= new? ? :create : :update
|
|
9
|
+
super context
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
6
13
|
def self.included(base) #:nodoc:
|
|
7
14
|
base.send :include, ::ActiveModel::Validations
|
|
8
15
|
base.send :include, ::ActiveModel::Validations::Callbacks
|
|
16
|
+
base.send :include, ValidationContext
|
|
9
17
|
end
|
|
10
18
|
end
|
|
11
19
|
end
|
data/lib/couch_potato/version.rb
CHANGED