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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +12 -8
  4. data/CHANGES.md +4 -0
  5. data/Gemfile +1 -1
  6. data/README.md +396 -276
  7. data/Rakefile +9 -9
  8. data/couch_potato-rspec.gemspec +20 -0
  9. data/couch_potato.gemspec +15 -16
  10. data/{active_support_4_0 → gemfiles/active_support_4_0} +3 -3
  11. data/{active_support_3_2 → gemfiles/active_support_4_1} +3 -2
  12. data/gemfiles/active_support_4_2 +11 -0
  13. data/lib/couch_potato-rspec.rb +3 -0
  14. data/lib/couch_potato.rb +3 -1
  15. data/lib/couch_potato/database.rb +42 -39
  16. data/lib/couch_potato/persistence/magic_timestamps.rb +5 -5
  17. data/lib/couch_potato/persistence/properties.rb +8 -2
  18. data/lib/couch_potato/persistence/simple_property.rb +11 -9
  19. data/lib/couch_potato/persistence/type_caster.rb +1 -1
  20. data/lib/couch_potato/railtie.rb +2 -0
  21. data/lib/couch_potato/version.rb +2 -1
  22. data/lib/couch_potato/view/base_view_spec.rb +18 -8
  23. data/lib/couch_potato/view/view_query.rb +2 -3
  24. data/spec/attachments_spec.rb +3 -3
  25. data/spec/callbacks_spec.rb +193 -113
  26. data/spec/conflict_handling_spec.rb +4 -4
  27. data/spec/create_spec.rb +5 -5
  28. data/spec/default_property_spec.rb +6 -6
  29. data/spec/destroy_spec.rb +5 -5
  30. data/spec/property_spec.rb +71 -61
  31. data/spec/rails_spec.rb +3 -3
  32. data/spec/railtie_spec.rb +12 -13
  33. data/spec/spec_helper.rb +3 -3
  34. data/spec/unit/active_model_compliance_spec.rb +16 -16
  35. data/spec/unit/attributes_spec.rb +36 -34
  36. data/spec/unit/base_view_spec_spec.rb +82 -35
  37. data/spec/unit/callbacks_spec.rb +2 -2
  38. data/spec/unit/couch_potato_spec.rb +3 -3
  39. data/spec/unit/create_spec.rb +12 -12
  40. data/spec/unit/custom_views_spec.rb +1 -1
  41. data/spec/unit/database_spec.rb +95 -95
  42. data/spec/unit/date_spec.rb +3 -3
  43. data/spec/unit/deep_dirty_attributes_spec.rb +104 -104
  44. data/spec/unit/dirty_attributes_spec.rb +19 -19
  45. data/spec/unit/forbidden_attributes_protection_spec.rb +4 -4
  46. data/spec/unit/initialize_spec.rb +37 -19
  47. data/spec/unit/json_spec.rb +4 -4
  48. data/spec/unit/lists_spec.rb +8 -8
  49. data/spec/unit/model_view_spec_spec.rb +14 -14
  50. data/spec/unit/persistence_spec.rb +6 -6
  51. data/spec/unit/properties_view_spec_spec.rb +4 -4
  52. data/spec/unit/rspec_matchers_spec.rb +73 -73
  53. data/spec/unit/rspec_stub_db_spec.rb +43 -42
  54. data/spec/unit/string_spec.rb +1 -1
  55. data/spec/unit/time_spec.rb +2 -2
  56. data/spec/unit/validation_spec.rb +1 -1
  57. data/spec/unit/view_query_spec.rb +54 -59
  58. data/spec/update_spec.rb +5 -5
  59. data/spec/view_updates_spec.rb +4 -4
  60. data/spec/views_spec.rb +43 -43
  61. metadata +18 -22
  62. data/lib/couch_potato/rspec.rb +0 -2
  63. data/lib/couch_potato/rspec/matchers.rb +0 -56
  64. data/lib/couch_potato/rspec/matchers/json2.js +0 -482
  65. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +0 -53
  66. data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +0 -166
  67. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +0 -61
  68. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +0 -48
  69. 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 "rspec/core/rake_task"
5
+ require 'rspec/core/rake_task'
6
6
 
7
- task :default => :spec
7
+ task default: :spec
8
8
 
9
- desc "Run functional specs"
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 "Run unit specs"
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 "Run all specs"
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
- ['3_2', '4_0'].each do |version|
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
@@ -1,28 +1,27 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
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 = "couch_potato"
7
- s.summary = %Q{Ruby persistence layer for CouchDB}
8
- s.email = "alex@upstre.am"
9
- s.homepage = "http://github.com/langalex/couch_potato"
10
- s.description = "Ruby persistence layer for CouchDB"
11
- s.authors = ["Alexander Lang"]
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', '~>1.2.0'
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.11.0'
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}/*`.split("\n")
26
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
- s.require_paths = ["lib"]
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.rc1'
4
- gem 'rails', '~>4.0.0.rc1'
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.2'
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: '..'
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activemodel', '~>4.2.0'
4
+ gem 'rails', '~>4.2.0'
5
+ if RUBY_PLATFORM =~ /java/
6
+ gem 'therubyrhino'
7
+ else
8
+ gem 'therubyracer'
9
+ end
10
+
11
+ gemspec name: 'couch_potato', path: '..'
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+
3
+ require 'couch_potato/rspec'
@@ -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, :split_design_documents_per_view, :default_language).new
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
- results = CouchPotato::View::ViewQuery.new(
41
- couchrest_database,
42
- spec.design_document,
43
- {spec.view_name => {
44
- :map => spec.map_function,
45
- :reduce => spec.reduce_function
46
- }
47
- },
48
- ({spec.list_name => spec.list_function} unless spec.list_name.nil?),
49
- spec.lib,
50
- spec.language
51
- ).query_view!(spec.view_parameters)
52
- processed_results = spec.process_results results
53
- processed_results.each do |document|
54
- document.database = self if document.respond_to?(:database=)
55
- end if processed_results.respond_to?(:each)
56
- processed_results
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 RestClient::Conflict => e
80
+ rescue CouchRest::Conflict => e
80
81
  if block
81
- document = document.reload
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 e
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 RestClient::Conflict
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
- if id.is_a?(Array)
119
- bulk_load id
120
- else
121
- begin
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
- res = couchrest_database.save_doc document.to_hash
215
- document._rev = res['rev']
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 {|property| yield property}
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.to_sym) || _document.has_key?(name.to_s)
8
- property = self.class.properties.find{|property| property.name == name}
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.to_s].nil? ? json[name.to_sym] : json[name.to_s]
31
- object.send "#{name}=", value
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 "#{name}" do
61
- load_attribute_from_document(name) unless instance_variable_defined?("@#{name}")
62
- value = instance_variable_get("@#{name}")
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("@#{name}", default)
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("@#{name}", typecasted_value)
85
+ self.instance_variable_set(ivar_name, typecasted_value)
84
86
  end
85
87
 
86
88
  define_method "#{name}?" do
@@ -27,7 +27,7 @@ module CouchPotato
27
27
  end
28
28
 
29
29
  def cast_native(value, type)
30
- if type && !value.instance_of?(type)
30
+ if type && !value.is_a?(type)
31
31
  if type == Fixnum
32
32
  BigDecimal.new(value.to_s.scan(NUMBER_REGEX).join).round unless value.blank?
33
33
  elsif type == Float
@@ -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
@@ -1,3 +1,4 @@
1
1
  module CouchPotato
2
- VERSION = "1.4.0"
2
+ VERSION = '1.6.3'
3
+ RSPEC_VERSION = '3.0.0'
3
4
  end