couch_potato 1.4.0 → 1.6.3
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/.ruby-version +1 -0
- data/.travis.yml +12 -8
- data/CHANGES.md +4 -0
- data/Gemfile +1 -1
- data/README.md +396 -276
- data/Rakefile +9 -9
- data/couch_potato-rspec.gemspec +20 -0
- data/couch_potato.gemspec +15 -16
- data/{active_support_4_0 → gemfiles/active_support_4_0} +3 -3
- data/{active_support_3_2 → gemfiles/active_support_4_1} +3 -2
- data/gemfiles/active_support_4_2 +11 -0
- data/lib/couch_potato-rspec.rb +3 -0
- data/lib/couch_potato.rb +3 -1
- data/lib/couch_potato/database.rb +42 -39
- data/lib/couch_potato/persistence/magic_timestamps.rb +5 -5
- data/lib/couch_potato/persistence/properties.rb +8 -2
- data/lib/couch_potato/persistence/simple_property.rb +11 -9
- data/lib/couch_potato/persistence/type_caster.rb +1 -1
- data/lib/couch_potato/railtie.rb +2 -0
- data/lib/couch_potato/version.rb +2 -1
- data/lib/couch_potato/view/base_view_spec.rb +18 -8
- data/lib/couch_potato/view/view_query.rb +2 -3
- data/spec/attachments_spec.rb +3 -3
- data/spec/callbacks_spec.rb +193 -113
- data/spec/conflict_handling_spec.rb +4 -4
- data/spec/create_spec.rb +5 -5
- data/spec/default_property_spec.rb +6 -6
- data/spec/destroy_spec.rb +5 -5
- data/spec/property_spec.rb +71 -61
- data/spec/rails_spec.rb +3 -3
- data/spec/railtie_spec.rb +12 -13
- data/spec/spec_helper.rb +3 -3
- data/spec/unit/active_model_compliance_spec.rb +16 -16
- data/spec/unit/attributes_spec.rb +36 -34
- data/spec/unit/base_view_spec_spec.rb +82 -35
- data/spec/unit/callbacks_spec.rb +2 -2
- data/spec/unit/couch_potato_spec.rb +3 -3
- data/spec/unit/create_spec.rb +12 -12
- data/spec/unit/custom_views_spec.rb +1 -1
- data/spec/unit/database_spec.rb +95 -95
- data/spec/unit/date_spec.rb +3 -3
- data/spec/unit/deep_dirty_attributes_spec.rb +104 -104
- data/spec/unit/dirty_attributes_spec.rb +19 -19
- data/spec/unit/forbidden_attributes_protection_spec.rb +4 -4
- data/spec/unit/initialize_spec.rb +37 -19
- data/spec/unit/json_spec.rb +4 -4
- data/spec/unit/lists_spec.rb +8 -8
- data/spec/unit/model_view_spec_spec.rb +14 -14
- data/spec/unit/persistence_spec.rb +6 -6
- data/spec/unit/properties_view_spec_spec.rb +4 -4
- data/spec/unit/rspec_matchers_spec.rb +73 -73
- data/spec/unit/rspec_stub_db_spec.rb +43 -42
- data/spec/unit/string_spec.rb +1 -1
- data/spec/unit/time_spec.rb +2 -2
- data/spec/unit/validation_spec.rb +1 -1
- data/spec/unit/view_query_spec.rb +54 -59
- data/spec/update_spec.rb +5 -5
- data/spec/view_updates_spec.rb +4 -4
- data/spec/views_spec.rb +43 -43
- metadata +18 -22
- data/lib/couch_potato/rspec.rb +0 -2
- data/lib/couch_potato/rspec/matchers.rb +0 -56
- data/lib/couch_potato/rspec/matchers/json2.js +0 -482
- data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +0 -53
- data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +0 -166
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +0 -61
- data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +0 -48
- data/lib/couch_potato/rspec/stub_db.rb +0 -57
data/Rakefile
CHANGED
@@ -1,34 +1,34 @@
|
|
1
1
|
require 'bundler'
|
2
|
-
Bundler::GemHelper.install_tasks
|
2
|
+
Bundler::GemHelper.install_tasks name: 'couch_potato'
|
3
3
|
|
4
4
|
require 'rake'
|
5
|
-
require
|
5
|
+
require 'rspec/core/rake_task'
|
6
6
|
|
7
|
-
task :
|
7
|
+
task default: :spec
|
8
8
|
|
9
|
-
desc
|
9
|
+
desc 'Run functional specs'
|
10
10
|
RSpec::Core::RakeTask.new(:spec_functional) do |spec|
|
11
11
|
spec.pattern = 'spec/*_spec.rb'
|
12
12
|
spec.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
13
13
|
end
|
14
14
|
|
15
|
-
desc
|
15
|
+
desc 'Run unit specs'
|
16
16
|
RSpec::Core::RakeTask.new(:spec_unit) do |spec|
|
17
17
|
spec.pattern = 'spec/unit/*_spec.rb'
|
18
18
|
spec.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
19
19
|
end
|
20
20
|
|
21
|
-
desc
|
21
|
+
desc 'Run all specs'
|
22
22
|
task :spec do
|
23
23
|
if ENV['TRAVIS'] # travis handles the environments for us
|
24
24
|
Rake::Task[:spec_unit].execute
|
25
25
|
Rake::Task[:spec_functional].execute
|
26
26
|
else
|
27
|
-
|
27
|
+
%w(4_0 4_1 4_2).each do |version|
|
28
28
|
Bundler.with_clean_env do
|
29
29
|
puts "Running tests with ActiveSupport #{version.sub('_', '.')}"
|
30
|
-
sh "env BUNDLE_GEMFILE=active_support_#{version} bundle install"
|
31
|
-
sh "env BUNDLE_GEMFILE=active_support_#{version} bundle exec rake spec_unit spec_functional"
|
30
|
+
sh "env BUNDLE_GEMFILE=gemfiles/active_support_#{version} bundle install"
|
31
|
+
sh "env BUNDLE_GEMFILE=gemfiles/active_support_#{version} bundle exec rake spec_unit spec_functional"
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'couch_potato/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'couch_potato-rspec'
|
6
|
+
s.summary = 'RSpec matchers for Couch Potato'
|
7
|
+
s.email = 'alex@upstre.am'
|
8
|
+
s.homepage = 'http://github.com/langalex/couch_potato'
|
9
|
+
s.description = 'RSpec matchers for Couch Potato'
|
10
|
+
s.authors = ['Alexander Lang']
|
11
|
+
s.version = CouchPotato::RSPEC_VERSION
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
|
14
|
+
s.add_dependency 'rspec', '~>3.0'
|
15
|
+
s.add_development_dependency 'rake'
|
16
|
+
|
17
|
+
s.files = `git ls-files | grep "lib/couch_potato/rspec"`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/* | grep rspec_matchers`.split("\n")
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
end
|
data/couch_potato.gemspec
CHANGED
@@ -1,28 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require "couch_potato/version"
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'couch_potato/version'
|
4
3
|
|
5
4
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
7
|
-
s.summary =
|
8
|
-
s.email =
|
9
|
-
s.homepage =
|
10
|
-
s.description =
|
11
|
-
s.authors = [
|
5
|
+
s.name = 'couch_potato'
|
6
|
+
s.summary = 'Ruby persistence layer for CouchDB'
|
7
|
+
s.email = 'alex@upstre.am'
|
8
|
+
s.homepage = 'http://github.com/langalex/couch_potato'
|
9
|
+
s.description = 'Ruby persistence layer for CouchDB'
|
10
|
+
s.authors = ['Alexander Lang']
|
12
11
|
s.version = CouchPotato::VERSION
|
13
12
|
s.platform = Gem::Platform::RUBY
|
14
13
|
|
15
14
|
s.add_dependency 'json', '~> 1.6'
|
16
|
-
s.add_dependency 'couchrest', '~>
|
17
|
-
s.add_dependency 'activemodel'
|
15
|
+
s.add_dependency 'couchrest', '~>2.0.0.rc3'
|
16
|
+
s.add_dependency 'activemodel', '~> 4.0'
|
18
17
|
|
19
|
-
s.add_development_dependency 'rspec', '~>2.
|
18
|
+
s.add_development_dependency 'rspec', '~>3.2.0'
|
20
19
|
s.add_development_dependency 'timecop'
|
21
20
|
s.add_development_dependency 'tzinfo'
|
22
21
|
s.add_development_dependency 'rake'
|
23
22
|
|
24
|
-
s.files = `git ls-files`.split("\n")
|
25
|
-
s.test_files = `git ls-files -- {test,spec,features}
|
26
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{
|
27
|
-
s.require_paths = [
|
23
|
+
s.files = `git ls-files | grep -v "lib/couch_potato/rspec"`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/* | grep -v rspec_matchers`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map {|f| File.basename(f) }
|
26
|
+
s.require_paths = ['lib']
|
28
27
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'activemodel', '~>4.0.0
|
4
|
-
gem 'rails', '~>4.0.0
|
3
|
+
gem 'activemodel', '~>4.0.0'
|
4
|
+
gem 'rails', '~>4.0.0'
|
5
5
|
if RUBY_PLATFORM =~ /java/
|
6
6
|
gem 'therubyrhino'
|
7
7
|
else
|
8
8
|
gem 'therubyracer'
|
9
9
|
end
|
10
10
|
|
11
|
-
gemspec
|
11
|
+
gemspec name: 'couch_potato', path: '..'
|
@@ -1,10 +1,11 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'activemodel', '~>
|
3
|
+
gem 'activemodel', '~>4.1.0'
|
4
|
+
gem 'rails', '~>4.1.0'
|
4
5
|
if RUBY_PLATFORM =~ /java/
|
5
6
|
gem 'therubyrhino'
|
6
7
|
else
|
7
8
|
gem 'therubyracer'
|
8
9
|
end
|
9
10
|
|
10
|
-
gemspec
|
11
|
+
gemspec name: 'couch_potato', path: '..'
|
data/lib/couch_potato.rb
CHANGED
@@ -7,8 +7,10 @@ JSON.create_id = 'ruby_class'
|
|
7
7
|
CouchRest.decode_json_objects = true
|
8
8
|
|
9
9
|
module CouchPotato
|
10
|
-
Config = Struct.new(:database_host, :database_name, :
|
10
|
+
Config = Struct.new(:database_host, :database_name, :digest_view_names,
|
11
|
+
:split_design_documents_per_view, :default_language).new
|
11
12
|
Config.split_design_documents_per_view = false
|
13
|
+
Config.digest_view_names = false
|
12
14
|
Config.default_language = :javascript
|
13
15
|
Config.database_host = "http://127.0.0.1:5984"
|
14
16
|
|
@@ -37,23 +37,25 @@ module CouchPotato
|
|
37
37
|
#
|
38
38
|
# db.view(User.all(keys: [1, 2, 3]))
|
39
39
|
def view(spec)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
40
|
+
ActiveSupport::Notifications.instrument('couch_potato.view') do
|
41
|
+
results = CouchPotato::View::ViewQuery.new(
|
42
|
+
couchrest_database,
|
43
|
+
spec.design_document,
|
44
|
+
{spec.view_name => {
|
45
|
+
:map => spec.map_function,
|
46
|
+
:reduce => spec.reduce_function
|
47
|
+
}
|
48
|
+
},
|
49
|
+
({spec.list_name => spec.list_function} unless spec.list_name.nil?),
|
50
|
+
spec.lib,
|
51
|
+
spec.language
|
52
|
+
).query_view!(spec.view_parameters)
|
53
|
+
processed_results = spec.process_results results
|
54
|
+
processed_results.each do |document|
|
55
|
+
document.database = self if document.respond_to?(:database=)
|
56
|
+
end if processed_results.respond_to?(:each)
|
57
|
+
processed_results
|
58
|
+
end
|
57
59
|
end
|
58
60
|
|
59
61
|
# returns the first result from a #view query or nil
|
@@ -71,22 +73,15 @@ module CouchPotato
|
|
71
73
|
# if passed a block will:
|
72
74
|
# * yield the object to be saved to the block and run if once before saving
|
73
75
|
# * on conflict: reload the document, run the block again and retry saving
|
74
|
-
def save_document(document, validate = true, &block)
|
75
|
-
retries = 0
|
76
|
+
def save_document(document, validate = true, retries = 0, &block)
|
76
77
|
begin
|
77
78
|
block.call document if block
|
78
79
|
save_document_without_conflict_handling(document, validate)
|
79
|
-
rescue
|
80
|
+
rescue CouchRest::Conflict => e
|
80
81
|
if block
|
81
|
-
document
|
82
|
-
if retries == 5
|
83
|
-
raise CouchPotato::Conflict.new
|
84
|
-
else
|
85
|
-
retries += 1
|
86
|
-
retry
|
87
|
-
end
|
82
|
+
handle_write_conflict document, validate, retries, &block
|
88
83
|
else
|
89
|
-
raise
|
84
|
+
raise CouchPotato::Conflict.new
|
90
85
|
end
|
91
86
|
end
|
92
87
|
end
|
@@ -101,7 +96,7 @@ module CouchPotato
|
|
101
96
|
def destroy_document(document)
|
102
97
|
begin
|
103
98
|
destroy_document_without_conflict_handling document
|
104
|
-
rescue
|
99
|
+
rescue CouchRest::Conflict
|
105
100
|
retry if document = document.reload
|
106
101
|
end
|
107
102
|
end
|
@@ -115,15 +110,13 @@ module CouchPotato
|
|
115
110
|
def load_document(id)
|
116
111
|
raise "Can't load a document without an id (got nil)" if id.nil?
|
117
112
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
113
|
+
ActiveSupport::Notifications.instrument('couch_potato.load') do
|
114
|
+
if id.is_a?(Array)
|
115
|
+
bulk_load id
|
116
|
+
else
|
122
117
|
instance = couchrest_database.get(id)
|
123
|
-
instance.database = self
|
118
|
+
instance.database = self if instance
|
124
119
|
instance
|
125
|
-
rescue(RestClient::ResourceNotFound)
|
126
|
-
nil
|
127
120
|
end
|
128
121
|
end
|
129
122
|
end
|
@@ -151,6 +144,15 @@ module CouchPotato
|
|
151
144
|
|
152
145
|
private
|
153
146
|
|
147
|
+
def handle_write_conflict(document, validate, retries, &block)
|
148
|
+
document = document.reload
|
149
|
+
if retries == 5
|
150
|
+
raise CouchPotato::Conflict.new
|
151
|
+
else
|
152
|
+
save_document document, validate, retries + 1, &block
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
154
156
|
def destroy_document_without_conflict_handling(document)
|
155
157
|
document.run_callbacks :destroy do
|
156
158
|
document._deleted = true
|
@@ -161,7 +163,6 @@ module CouchPotato
|
|
161
163
|
end
|
162
164
|
|
163
165
|
def save_document_without_conflict_handling(document, validate = true)
|
164
|
-
return true unless document.dirty? || document.new?
|
165
166
|
if document.new?
|
166
167
|
create_document(document, validate)
|
167
168
|
else
|
@@ -211,8 +212,10 @@ module CouchPotato
|
|
211
212
|
|
212
213
|
return false if false == document.run_callbacks(:save) do
|
213
214
|
return false if false == document.run_callbacks(:update) do
|
214
|
-
|
215
|
-
|
215
|
+
if document.dirty?
|
216
|
+
res = couchrest_database.save_doc document.to_hash
|
217
|
+
document._rev = res['rev']
|
218
|
+
end
|
216
219
|
end
|
217
220
|
end
|
218
221
|
true
|
@@ -6,18 +6,18 @@ module CouchPotato
|
|
6
6
|
base.instance_eval do
|
7
7
|
property :created_at, :type => Time
|
8
8
|
property :updated_at, :type => Time
|
9
|
-
|
9
|
+
|
10
10
|
before_create lambda {|model|
|
11
11
|
model.created_at ||= (Time.zone || Time).now
|
12
|
-
@changed_attributes.delete 'created_at'
|
12
|
+
@changed_attributes.try :delete, 'created_at'
|
13
13
|
model.updated_at ||= (Time.zone || Time).now
|
14
|
-
@changed_attributes.delete 'updated_at'
|
14
|
+
@changed_attributes.try :delete, 'updated_at'
|
15
15
|
}
|
16
16
|
before_update lambda {|model|
|
17
17
|
model.updated_at = (Time.zone || Time).now
|
18
|
-
@changed_attributes.delete 'updated_at'
|
18
|
+
@changed_attributes.try :delete, 'updated_at'
|
19
19
|
}
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
|
-
end
|
23
|
+
end
|
@@ -12,16 +12,22 @@ module CouchPotato
|
|
12
12
|
def initialize(clazz)
|
13
13
|
@clazz = clazz
|
14
14
|
@list = []
|
15
|
+
@hash = {}
|
15
16
|
end
|
16
17
|
|
17
|
-
def each
|
18
|
-
(list + inherited_properties).each
|
18
|
+
def each(&block)
|
19
|
+
(list + inherited_properties).each(&block)
|
19
20
|
end
|
20
21
|
|
21
22
|
def <<(property)
|
23
|
+
@hash[property.name] = property
|
22
24
|
@list << property
|
23
25
|
end
|
24
26
|
|
27
|
+
def find_property(name)
|
28
|
+
@hash[name]
|
29
|
+
end
|
30
|
+
|
25
31
|
# XXX
|
26
32
|
def inspect
|
27
33
|
list.map(&:name).inspect
|
@@ -4,8 +4,8 @@ module CouchPotato
|
|
4
4
|
private
|
5
5
|
|
6
6
|
def load_attribute_from_document(name)
|
7
|
-
if _document.has_key?(name
|
8
|
-
property = self.class.properties.
|
7
|
+
if _document.has_key?(name)
|
8
|
+
property = self.class.properties.find_property name
|
9
9
|
@skip_dirty_tracking = true
|
10
10
|
value = property.build(self, _document)
|
11
11
|
@skip_dirty_tracking = false
|
@@ -19,6 +19,7 @@ module CouchPotato
|
|
19
19
|
|
20
20
|
def initialize(owner_clazz, name, options = {})
|
21
21
|
self.name = name
|
22
|
+
@setter_name = "#{name}="
|
22
23
|
self.type = options[:type]
|
23
24
|
@type_caster = TypeCaster.new
|
24
25
|
owner_clazz.send :include, PropertyMethods unless owner_clazz.ancestors.include?(PropertyMethods)
|
@@ -27,8 +28,8 @@ module CouchPotato
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def build(object, json)
|
30
|
-
value = json[name
|
31
|
-
object.send
|
31
|
+
value = json[name]
|
32
|
+
object.send @setter_name, value
|
32
33
|
end
|
33
34
|
|
34
35
|
def dirty?(object)
|
@@ -56,10 +57,11 @@ module CouchPotato
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def define_accessors(base, name, options)
|
60
|
+
ivar_name = "@#{name}".freeze
|
59
61
|
base.class_eval do
|
60
|
-
define_method
|
61
|
-
load_attribute_from_document(name) unless instance_variable_defined?(
|
62
|
-
value = instance_variable_get(
|
62
|
+
define_method name do
|
63
|
+
load_attribute_from_document(name) unless instance_variable_defined?(ivar_name)
|
64
|
+
value = instance_variable_get(ivar_name)
|
63
65
|
if value.nil? && !options[:default].nil?
|
64
66
|
default = if options[:default].respond_to?(:call)
|
65
67
|
if options[:default].arity == 1
|
@@ -70,7 +72,7 @@ module CouchPotato
|
|
70
72
|
else
|
71
73
|
clone_attribute(options[:default])
|
72
74
|
end
|
73
|
-
self.instance_variable_set(
|
75
|
+
self.instance_variable_set(ivar_name, default)
|
74
76
|
default
|
75
77
|
else
|
76
78
|
value
|
@@ -80,7 +82,7 @@ module CouchPotato
|
|
80
82
|
define_method "#{name}=" do |value|
|
81
83
|
typecasted_value = type_caster.cast(value, options[:type])
|
82
84
|
send("#{name}_will_change!") unless @skip_dirty_tracking || typecasted_value == send(name)
|
83
|
-
self.instance_variable_set(
|
85
|
+
self.instance_variable_set(ivar_name, typecasted_value)
|
84
86
|
end
|
85
87
|
|
86
88
|
define_method "#{name}?" do
|
data/lib/couch_potato/railtie.rb
CHANGED
@@ -5,12 +5,14 @@ module CouchPotato
|
|
5
5
|
def self.rails_init
|
6
6
|
path = Rails.root.join('config/couchdb.yml')
|
7
7
|
if File.exist?(path)
|
8
|
+
require 'yaml'
|
8
9
|
config = YAML::load(ERB.new(File.read(path)).result)[Rails.env]
|
9
10
|
if config.is_a?(String)
|
10
11
|
CouchPotato::Config.database_name = config
|
11
12
|
else
|
12
13
|
CouchPotato::Config.database_name = config['database']
|
13
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']
|
14
16
|
CouchPotato::Config.default_language = config['default_language'] if config['default_language']
|
15
17
|
end
|
16
18
|
else
|
data/lib/couch_potato/version.rb
CHANGED