couch_potato 1.4.0 → 1.6.3

Sign up to get free protection for your applications and to get access to all the features.
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