couch_potato 1.7.0 → 1.10.1
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 +50 -0
- data/.gitignore +3 -0
- data/CHANGES.md +180 -130
- data/Gemfile +4 -0
- data/README.md +61 -85
- data/Rakefile +11 -10
- data/couch_potato-rspec.gemspec +2 -1
- data/couch_potato.gemspec +9 -7
- data/gemfiles/active_support_5_0 +6 -0
- data/gemfiles/active_support_5_1 +7 -0
- data/gemfiles/active_support_5_2 +6 -0
- data/gemfiles/active_support_6_0 +6 -0
- data/gemfiles/active_support_6_1 +6 -0
- data/gemfiles/active_support_7_0 +6 -0
- data/lib/couch_potato/database.rb +170 -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/simple_property.rb +0 -4
- data/lib/couch_potato/persistence/type_caster.rb +11 -6
- data/lib/couch_potato/persistence.rb +0 -1
- data/lib/couch_potato/railtie.rb +6 -11
- 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 +10 -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 +239 -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/unit/view_query_spec.rb +12 -7
- data/spec/views_spec.rb +214 -103
- data/vendor/pouchdb-collate/LICENSE +202 -0
- data/vendor/pouchdb-collate/pouchdb-collate.js +430 -0
- metadata +47 -36
- data/.ruby-version +0 -1
- data/.travis.yml +0 -21
- 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
|
@@ -171,11 +269,13 @@ module CouchPotato
|
|
171
269
|
end
|
172
270
|
|
173
271
|
def bulk_load(ids)
|
272
|
+
return [] if ids.empty?
|
273
|
+
|
174
274
|
response = couchrest_database.bulk_load ids
|
175
|
-
docs = response['rows'].map{|row| row[
|
176
|
-
docs.each
|
275
|
+
docs = response['rows'].map { |row| row['doc'] }.compact
|
276
|
+
docs.each do |doc|
|
177
277
|
doc.database = self if doc.respond_to?(:database=)
|
178
|
-
|
278
|
+
end
|
179
279
|
end
|
180
280
|
|
181
281
|
def create_document(document, validate)
|
@@ -183,51 +283,50 @@ module CouchPotato
|
|
183
283
|
|
184
284
|
if validate
|
185
285
|
document.errors.clear
|
186
|
-
return false if
|
187
|
-
return false if
|
286
|
+
return false if document.run_callbacks(:validation_on_save) do
|
287
|
+
return false if document.run_callbacks(:validation_on_create) do
|
188
288
|
return false unless valid_document?(document)
|
189
|
-
end
|
190
|
-
end
|
289
|
+
end == false
|
290
|
+
end == false
|
191
291
|
end
|
192
292
|
|
193
|
-
return false if
|
194
|
-
return false if
|
293
|
+
return false if document.run_callbacks(:save) do
|
294
|
+
return false if document.run_callbacks(:create) do
|
195
295
|
res = couchrest_database.save_doc document.to_hash
|
196
296
|
document._rev = res['rev']
|
197
297
|
document._id = res['id']
|
198
|
-
end
|
199
|
-
end
|
298
|
+
end == false
|
299
|
+
end == false
|
300
|
+
|
200
301
|
true
|
201
302
|
end
|
202
303
|
|
203
304
|
def update_document(document, validate)
|
204
305
|
if validate
|
205
306
|
document.errors.clear
|
206
|
-
return false if
|
207
|
-
return false if
|
307
|
+
return false if document.run_callbacks(:validation_on_save) do
|
308
|
+
return false if document.run_callbacks(:validation_on_update) do
|
208
309
|
return false unless valid_document?(document)
|
209
|
-
end
|
210
|
-
end
|
310
|
+
end == false
|
311
|
+
end == false
|
211
312
|
end
|
212
313
|
|
213
|
-
return false if
|
214
|
-
return false if
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
end
|
314
|
+
return false if document.run_callbacks(:save) do
|
315
|
+
return false if document.run_callbacks(:update) do
|
316
|
+
res = couchrest_database.save_doc document.to_hash
|
317
|
+
document._rev = res['rev']
|
318
|
+
end == false
|
319
|
+
end == false
|
320
|
+
|
221
321
|
true
|
222
322
|
end
|
223
323
|
|
224
324
|
def valid_document?(document)
|
225
|
-
|
226
|
-
errors.instance_variable_set("@messages", errors.messages.dup) if errors.respond_to?(:messages)
|
325
|
+
original_errors_hash = document.errors.to_hash
|
227
326
|
document.valid?
|
228
|
-
|
327
|
+
original_errors_hash.each do |k, v|
|
229
328
|
if v.respond_to?(:each)
|
230
|
-
v.each {|message| document.errors.add(k, message)}
|
329
|
+
v.each { |message| document.errors.add(k, message) }
|
231
330
|
else
|
232
331
|
document.errors.add(k, v)
|
233
332
|
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
|
@@ -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,7 +6,6 @@ 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'
|
data/lib/couch_potato/railtie.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'erb'
|
2
4
|
|
3
5
|
module CouchPotato
|
@@ -6,23 +8,16 @@ module CouchPotato
|
|
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
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CouchPotato
|
2
4
|
module View
|
3
5
|
class BaseViewSpec
|
@@ -7,7 +9,7 @@ module CouchPotato
|
|
7
9
|
private :klass, :options
|
8
10
|
|
9
11
|
def initialize(klass, view_name, options, view_parameters)
|
10
|
-
normalized_view_parameters = normalize_view_parameters view_parameters
|
12
|
+
normalized_view_parameters = ViewParameters.normalize_view_parameters view_parameters
|
11
13
|
|
12
14
|
@list_name = normalized_view_parameters.delete(:list) || options[:list]
|
13
15
|
@language = options[:language] || Config.default_language
|
@@ -16,13 +18,13 @@ module CouchPotato
|
|
16
18
|
@klass = klass
|
17
19
|
@options = options
|
18
20
|
@view_name = compute_view_name(view_name,
|
19
|
-
|
21
|
+
options.key?(:digest_view_name) ? options[:digest_view_name] : Config.digest_view_names)
|
20
22
|
@design_document = translate_to_design_doc_name(klass.to_s, @view_name, @list_name)
|
21
23
|
@list_params = normalized_view_parameters.delete :list_params
|
22
24
|
|
23
25
|
@list_function = klass.lists(@list_name) if @list_name
|
24
26
|
@view_parameters = {}
|
25
|
-
[
|
27
|
+
%i[group include_docs descending group_level limit].each do |key|
|
26
28
|
@view_parameters[key] = options[key] if options.include?(key)
|
27
29
|
end
|
28
30
|
@view_parameters.merge!(normalized_view_parameters)
|
@@ -47,40 +49,14 @@ module CouchPotato
|
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
|
-
def normalize_view_parameters(params)
|
51
|
-
hash = wrap_in_hash params
|
52
|
-
remove_nil_stale(replace_range_key(hash))
|
53
|
-
end
|
54
|
-
|
55
|
-
def remove_nil_stale(params)
|
56
|
-
params.reject{|name, value| name.to_s == 'stale' && value.nil?}
|
57
|
-
end
|
58
|
-
|
59
|
-
def wrap_in_hash(params)
|
60
|
-
if params.is_a?(Hash)
|
61
|
-
params
|
62
|
-
else
|
63
|
-
{:key => params}
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def replace_range_key(params)
|
68
|
-
if((key = params[:key]).is_a?(Range))
|
69
|
-
params.delete :key
|
70
|
-
params[:startkey] = key.first
|
71
|
-
params[:endkey] = key.last
|
72
|
-
end
|
73
|
-
params
|
74
|
-
end
|
75
|
-
|
76
52
|
def assert_valid_view_parameters(params)
|
77
53
|
params.keys.each do |key|
|
78
|
-
|
54
|
+
raise ArgumentError, "invalid view parameter: #{key}" unless valid_view_parameters.include?(key.to_s)
|
79
55
|
end
|
80
56
|
end
|
81
57
|
|
82
58
|
def valid_view_parameters
|
83
|
-
%w
|
59
|
+
%w[list_params key keys startkey startkey_docid endkey endkey_docid limit stale descending skip group group_level reduce include_docs inclusive_end]
|
84
60
|
end
|
85
61
|
|
86
62
|
def translate_to_design_doc_name(klass_name, view_name, list_name)
|
@@ -91,7 +67,7 @@ module CouchPotato
|
|
91
67
|
doc_name = klass_name.downcase
|
92
68
|
|
93
69
|
if CouchPotato::Config.split_design_documents_per_view
|
94
|
-
doc_name += "_view_#{view_name}"
|
70
|
+
doc_name += "_view_#{view_name}" if view_name.present?
|
95
71
|
doc_name += "_list_#{list_name}" if list_name.present?
|
96
72
|
end
|
97
73
|
doc_name
|