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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +50 -0
  3. data/.gitignore +3 -0
  4. data/CHANGES.md +180 -130
  5. data/Gemfile +4 -0
  6. data/README.md +61 -85
  7. data/Rakefile +11 -10
  8. data/couch_potato-rspec.gemspec +2 -1
  9. data/couch_potato.gemspec +9 -7
  10. data/gemfiles/active_support_5_0 +6 -0
  11. data/gemfiles/active_support_5_1 +7 -0
  12. data/gemfiles/active_support_5_2 +6 -0
  13. data/gemfiles/active_support_6_0 +6 -0
  14. data/gemfiles/active_support_6_1 +6 -0
  15. data/gemfiles/active_support_7_0 +6 -0
  16. data/lib/couch_potato/database.rb +170 -71
  17. data/lib/couch_potato/persistence/dirty_attributes.rb +3 -21
  18. data/lib/couch_potato/persistence/magic_timestamps.rb +3 -3
  19. data/lib/couch_potato/persistence/properties.rb +15 -10
  20. data/lib/couch_potato/persistence/simple_property.rb +0 -4
  21. data/lib/couch_potato/persistence/type_caster.rb +11 -6
  22. data/lib/couch_potato/persistence.rb +0 -1
  23. data/lib/couch_potato/railtie.rb +6 -11
  24. data/lib/couch_potato/validation.rb +8 -0
  25. data/lib/couch_potato/version.rb +2 -2
  26. data/lib/couch_potato/view/base_view_spec.rb +8 -32
  27. data/lib/couch_potato/view/custom_views.rb +4 -3
  28. data/lib/couch_potato/view/flex_view_spec.rb +121 -0
  29. data/lib/couch_potato/view/view_parameters.rb +34 -0
  30. data/lib/couch_potato.rb +37 -16
  31. data/spec/callbacks_spec.rb +45 -19
  32. data/spec/conflict_handling_spec.rb +1 -2
  33. data/spec/property_spec.rb +12 -3
  34. data/spec/railtie_spec.rb +10 -0
  35. data/spec/spec_helper.rb +4 -3
  36. data/spec/unit/active_model_compliance_spec.rb +7 -3
  37. data/spec/unit/attributes_spec.rb +54 -1
  38. data/spec/unit/caching_spec.rb +105 -0
  39. data/spec/unit/couch_potato_spec.rb +70 -5
  40. data/spec/unit/create_spec.rb +5 -4
  41. data/spec/unit/database_spec.rb +239 -135
  42. data/spec/unit/dirty_attributes_spec.rb +5 -26
  43. data/spec/unit/flex_view_spec_spec.rb +17 -0
  44. data/spec/unit/model_view_spec_spec.rb +1 -1
  45. data/spec/unit/rspec_stub_db_spec.rb +31 -0
  46. data/spec/unit/validation_spec.rb +42 -2
  47. data/spec/unit/view_query_spec.rb +12 -7
  48. data/spec/views_spec.rb +214 -103
  49. data/vendor/pouchdb-collate/LICENSE +202 -0
  50. data/vendor/pouchdb-collate/pouchdb-collate.js +430 -0
  51. metadata +47 -36
  52. data/.ruby-version +0 -1
  53. data/.travis.yml +0 -21
  54. data/gemfiles/active_support_4_0 +0 -11
  55. data/gemfiles/active_support_4_1 +0 -11
  56. data/gemfiles/active_support_4_2 +0 -11
  57. data/lib/couch_potato/persistence/deep_dirty_attributes.rb +0 -180
  58. data/spec/unit/deep_dirty_attributes_spec.rb +0 -434
@@ -1,9 +1,10 @@
1
1
  require 'couch_potato/view/base_view_spec'
2
+ require 'couch_potato/view/flex_view_spec'
2
3
  require 'couch_potato/view/model_view_spec'
3
4
  require 'couch_potato/view/properties_view_spec'
4
5
  require 'couch_potato/view/custom_view_spec'
5
6
  require 'couch_potato/view/raw_view_spec'
6
-
7
+ require 'couch_potato/view/view_parameters'
7
8
 
8
9
  module CouchPotato
9
10
  module View
@@ -25,7 +26,7 @@ module CouchPotato
25
26
  def execute_view(view_name, view_parameters) #:nodoc:
26
27
  view_spec_class(views(view_name)[:type]).new(self, view_name, views(view_name), view_parameters)
27
28
  end
28
-
29
+
29
30
  # Declare a CouchDB view, for examples on how to use see the *ViewSpec classes in CouchPotato::View
30
31
  def view(view_name, options)
31
32
  view_name = view_name.to_s
@@ -42,7 +43,7 @@ module CouchPotato
42
43
  CouchPotato::View.const_get("#{name}ViewSpec")
43
44
  end
44
45
  end
45
-
46
+
46
47
  def _find_view(view) #:nodoc:
47
48
  (@views && @views[view]) || (superclass._find_view(view) if superclass.respond_to?(:_find_view))
48
49
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CouchPotato
4
+ module View
5
+ # A flexible view spec.
6
+ # It allows to either just define a key and option conditions like
7
+ # the model view spec or custom map/reduce functions.
8
+ # In addition, it returns a result object that allows convenient access
9
+ # to either the raw result, the keys, values, ids or docs. The result object can
10
+ # be extended with custom module, too.
11
+ # Examples:
12
+ # class Thing
13
+ # module ResultsExt
14
+ # def average_time
15
+ # keys.sum / keys.size # can access other result methods
16
+ # end
17
+ # end
18
+ # property :time
19
+ # view :by_time, type: :flex, key: :time, extend_results: ResultsExt
20
+ # view :by_custom_time, type: :flex,
21
+ # reduce: '_sum'
22
+ # map: <<~JS
23
+ # function(doc) {
24
+ # emit(doc.time, 1);
25
+ # }
26
+ # JS
27
+ # end
28
+ #
29
+ # usage:
30
+ # irb> result = db.view Thing.by_time
31
+ # irb> result.raw # raw CouchDB results
32
+ # irb> result.ids # ids of rows
33
+ # irb> result.keys # keys emitted in map function
34
+ # irb> result.values # values emitted in map function
35
+ # irb> result.average_time # custom method from ResultsExt module
36
+ # irb> db.view(Thing.by_time(include_docs: true)).docs # documents
37
+ # irb> db.view(Thing.by_time(reduce: true)).reduce_value # value of first row, i.e. result of the reduce function (without grouping)
38
+ class FlexViewSpec
39
+ attr_reader :klass
40
+
41
+ class Results
42
+ attr_accessor :database # set by database
43
+
44
+ def initialize(raw_results)
45
+ @raw_results = raw_results
46
+ end
47
+
48
+ def raw
49
+ @raw_results
50
+ end
51
+
52
+ def ids
53
+ rows.map { |row| row['id'] }
54
+ end
55
+
56
+ def keys
57
+ rows.map { |row| row['key'] }
58
+ end
59
+
60
+ def values
61
+ rows.map { |row| row['value'] }
62
+ end
63
+
64
+ def reduce_value
65
+ rows.dig(0, 'value')
66
+ end
67
+
68
+ # returns a count from a CouchDB reduce. returns 0 when the result
69
+ # set is empty (which would result in `nil` when calling #reduce_value).
70
+ # you still have to pass reduce=true to the view call.
71
+ def reduce_count
72
+ reduce_value || 0
73
+ end
74
+
75
+ def docs
76
+ rows.map do |row|
77
+ doc = row['doc']
78
+ doc.database = database if doc.respond_to?(:database=)
79
+ doc
80
+ end
81
+ end
82
+
83
+ def rows
84
+ @raw_results['rows']
85
+ end
86
+ end
87
+
88
+ def initialize(klass, view_name, options, view_parameters)
89
+ @extend_results_module = options[:extend_results]
90
+ @klass = klass
91
+ @view_name = view_name
92
+ @options = options.except(:extend_results)
93
+ @view_parameters = view_parameters
94
+ end
95
+
96
+ delegate :view_name, :view_parameters, :design_document, :map_function,
97
+ :reduce_function, :list_name, :lib, :language, to: :view_spec_delegate
98
+
99
+ def process_results(results)
100
+ results = Results.new(results)
101
+ results.extend @extend_results_module if @extend_results_module
102
+ results
103
+ end
104
+
105
+ private
106
+
107
+ def view_spec_delegate
108
+ unless @view_spec_delegate
109
+ view_spec_class = @options[:map] ? RawViewSpec : ModelViewSpec
110
+ @view_spec_delegate = view_spec_class.new(
111
+ @klass, @view_name, @options,
112
+ ViewParameters
113
+ .normalize_view_parameters(@view_parameters)
114
+ .reverse_merge(reduce: false, include_docs: false)
115
+ )
116
+ end
117
+ @view_spec_delegate
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,34 @@
1
+ module CouchPotato
2
+ module View
3
+ module ViewParameters
4
+ module_function
5
+
6
+ def normalize_view_parameters(params)
7
+ hash = wrap_in_hash params
8
+ remove_nil_stale(replace_range_key(hash))
9
+ end
10
+
11
+ def remove_nil_stale(params)
12
+ params.reject{|name, value| name.to_s == 'stale' && value.nil?}
13
+ end
14
+
15
+ def wrap_in_hash(params)
16
+ if params.is_a?(Hash)
17
+ params
18
+ else
19
+ {:key => params}
20
+ end
21
+ end
22
+
23
+ def replace_range_key(params)
24
+ if((key = params[:key]).is_a?(Range))
25
+ params.delete :key
26
+ params[:startkey] = key.first
27
+ params[:endkey] = key.last
28
+ end
29
+ params
30
+ end
31
+
32
+ end
33
+ end
34
+ end
data/lib/couch_potato.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'couchrest'
2
4
  require 'json'
3
5
 
@@ -8,15 +10,30 @@ CouchRest.decode_json_objects = true
8
10
 
9
11
  module CouchPotato
10
12
  Config = Struct.new(:database_host, :database_name, :digest_view_names,
11
- :split_design_documents_per_view, :default_language).new
13
+ :split_design_documents_per_view, :default_language, :additional_databases).new
12
14
  Config.split_design_documents_per_view = false
13
15
  Config.digest_view_names = false
14
16
  Config.default_language = :javascript
15
- Config.database_host = "http://127.0.0.1:5984"
17
+ Config.database_host = 'http://127.0.0.1:5984'
18
+ Config.additional_databases = {}
16
19
 
17
20
  class NotFound < StandardError; end
18
21
  class Conflict < StandardError; end
19
22
 
23
+ def self.configure(config)
24
+ if config.is_a?(String)
25
+ Config.database_name = config
26
+ else
27
+ config = config.stringify_keys
28
+ Config.database_name = config['database']
29
+ Config.database_host = config['database_host'] if config['database_host']
30
+ Config.additional_databases = config['additional_databases'].stringify_keys if config['additional_databases']
31
+ Config.split_design_documents_per_view = config['split_design_documents_per_view'] if config['split_design_documents_per_view']
32
+ Config.digest_view_names = config['digest_view_names'] if config['digest_view_names']
33
+ Config.default_language = config['default_language'] if config['default_language']
34
+ end
35
+ end
36
+
20
37
  # returns all the classes that include the CouchPotato::Persistence module
21
38
  def self.models
22
39
  @models ||= []
@@ -25,22 +42,27 @@ module CouchPotato
25
42
 
26
43
  # Returns a database instance which you can then use to create objects and query views. You have to set the CouchPotato::Config.database_name before this works.
27
44
  def self.database
28
- @@__database ||= Database.new(self.couchrest_database)
45
+ Thread.current[:__couch_potato_database] ||= Database.new(couchrest_database)
29
46
  end
30
47
 
31
48
  # Returns the underlying CouchRest database object if you want low level access to your CouchDB. You have to set the CouchPotato::Config.database_name before this works.
32
49
  def self.couchrest_database
33
- @@__couchrest_database ||= CouchRest.database(full_url_to_database(CouchPotato::Config.database_name, CouchPotato::Config.database_host))
50
+ Thread.current[:__couchrest_database] ||= CouchRest.database(full_url_to_database(CouchPotato::Config.database_name, CouchPotato::Config.database_host))
34
51
  end
35
52
 
36
53
  # Returns a specific database instance
37
54
  def self.use(database_name)
38
- @@__databases ||= {}
39
- @@__databases["#{database_name}"] = Database.new(couchrest_database_for_name!(database_name)) unless @@__databases["#{database_name}"]
40
- @@__databases["#{database_name}"]
55
+ resolved_database_name = resolve_database_name(database_name)
56
+ Thread.current[:__couch_potato_databases] ||= {}
57
+ Thread.current[:__couch_potato_databases][resolved_database_name] ||= Database.new(couchrest_database_for_name!(resolved_database_name), name: database_name)
41
58
  end
42
59
 
43
- # Executes a block of code and yields a datbase with the given name.
60
+ # resolves a name to a database name/full url configured under additional databases
61
+ def self.resolve_database_name(database_name)
62
+ Config.additional_databases[database_name] || database_name
63
+ end
64
+
65
+ # Executes a block of code and yields a database with the given name.
44
66
  #
45
67
  # example:
46
68
  # CouchPotato.with_database('couch_customer') do |couch|
@@ -48,25 +70,24 @@ module CouchPotato
48
70
  # end
49
71
  #
50
72
  def self.with_database(database_name)
51
- @@__databases ||= {}
52
- @@__databases["#{database_name}"] = Database.new(couchrest_database_for_name(database_name)) unless @@__databases["#{database_name}"]
53
- yield(@@__databases["#{database_name}"])
73
+ yield use(database_name)
54
74
  end
55
75
 
56
76
  # Returns a CouchRest-Database for directly accessing that functionality.
57
77
  def self.couchrest_database_for_name(database_name)
58
- CouchRest.database(full_url_to_database(database_name, CouchPotato::Config.database_host))
78
+ Thread.current[:__couchrest_databases] ||= {}
79
+ Thread.current[:__couchrest_databases][database_name] ||= CouchRest.database(full_url_to_database(database_name, CouchPotato::Config.database_host))
59
80
  end
60
81
 
61
82
  # Creates a CouchRest-Database for directly accessing that functionality.
62
83
  def self.couchrest_database_for_name!(database_name)
63
- CouchRest.database!(full_url_to_database(database_name))
84
+ Thread.current[:__couchrest_databases] ||= {}
85
+ Thread.current[:__couchrest_databases][database_name] ||= CouchRest.database!(full_url_to_database(database_name))
64
86
  end
65
87
 
66
- private
67
-
68
- def self.full_url_to_database(database_name=CouchPotato::Config.database_name, database_host = CouchPotato::Config.database_host)
88
+ def self.full_url_to_database(database_name = CouchPotato::Config.database_name, database_host = CouchPotato::Config.database_host)
69
89
  raise('No Database configured. Set CouchPotato::Config.database_name') unless database_name
90
+
70
91
  if database_name.match(%r{https?://})
71
92
  database_name
72
93
  else
@@ -382,13 +382,13 @@ describe "validation callbacks and filter halt" do
382
382
 
383
383
  property :name
384
384
  before_validation :check_name
385
- before_validation_on_update :return_false_from_callback
385
+ before_validation_on_update :abort_callback
386
386
 
387
387
  def check_name
388
388
  errors.add(:name, 'should be Paul') unless name == "Paul"
389
389
  end
390
390
 
391
- def return_false_from_callback
391
+ def abort_callback
392
392
  false
393
393
  end
394
394
  end
@@ -398,14 +398,14 @@ describe "validation callbacks and filter halt" do
398
398
 
399
399
  property :name
400
400
  before_validation :check_name
401
- before_validation_on_save :return_false_from_callback
402
- before_validation_on_create :return_false_from_callback
401
+ before_validation_on_save :abort_callback
402
+ before_validation_on_create :abort_callback
403
403
 
404
404
  def check_name
405
405
  errors.add(:name, 'should be Paul') unless name == "Paul"
406
406
  end
407
407
 
408
- def return_false_from_callback
408
+ def abort_callback
409
409
  false
410
410
  end
411
411
  end
@@ -414,9 +414,9 @@ describe "validation callbacks and filter halt" do
414
414
  include CouchPotato::Persistence
415
415
 
416
416
  property :name
417
- before_update :return_false_from_callback
417
+ before_update :abort_callback
418
418
 
419
- def return_false_from_callback
419
+ def abort_callback
420
420
  false
421
421
  end
422
422
  end
@@ -425,10 +425,10 @@ describe "validation callbacks and filter halt" do
425
425
  include CouchPotato::Persistence
426
426
 
427
427
  property :name
428
- before_save :return_false_from_callback
429
- before_create :return_false_from_callback
428
+ before_save :abort_callback
429
+ before_create :abort_callback
430
430
 
431
- def return_false_from_callback
431
+ def abort_callback
432
432
  false
433
433
  end
434
434
  end
@@ -450,16 +450,42 @@ describe "validation callbacks and filter halt" do
450
450
  expect(@db.save_document(@user)).to eq(false)
451
451
  end
452
452
 
453
- it "should return false on saving a document when a before update filter returned false" do
454
- @user = FilterSaveUpdateUser.new(:name => "Paul")
455
- expect(@db.save_document(@user)).to eq(true)
456
- @user.name = 'Bert'
457
- expect(@db.save_document(@user)).to eq(false)
458
- end
453
+ if ActiveModel.version.segments.first < 5
454
+ it "should return false on saving a document when a before update filter returned false" do
455
+ @user = FilterSaveUpdateUser.new(:name => "Paul")
456
+ expect(@db.save_document(@user)).to eq(true)
457
+ @user.name = 'Bert'
458
+ expect(@db.save_document(@user)).to eq(false)
459
+ end
459
460
 
460
- it "should return false on saving a document when a before save or before create filter returned false" do
461
- @user = FilterSaveCreateUser.new(:name => "Bert")
462
- expect(@db.save_document(@user)).to eq(false)
461
+ it "should return false on saving a document when a before save or before create filter returned false" do
462
+ @user = FilterSaveCreateUser.new(:name => "Bert")
463
+ expect(@db.save_document(@user)).to eq(false)
464
+ end
465
+ else
466
+ class FilterSaveCreateUser5 < FilterSaveCreateUser
467
+ def abort_callback
468
+ throw :abort
469
+ end
470
+ end
471
+
472
+ class FilterSaveUpdateUser5 < FilterSaveUpdateUser
473
+ def abort_callback
474
+ throw :abort
475
+ end
476
+ end
477
+
478
+ it "returns false on saving a document when a before update filter throws :abort" do
479
+ @user = FilterSaveUpdateUser5.new(:name => "Paul")
480
+ expect(@db.save_document(@user)).to eq(true)
481
+ @user.name = 'Bert'
482
+ expect(@db.save_document(@user)).to eq(false)
483
+ end
484
+
485
+ it "returns false on saving a document when a before save or before create filter throws :abort" do
486
+ @user = FilterSaveCreateUser5.new(:name => "Bert")
487
+ expect(@db.save_document(@user)).to eq(false)
488
+ end
463
489
  end
464
490
 
465
491
  end
@@ -14,12 +14,11 @@ describe 'conflict handling' do
14
14
 
15
15
  db.couchrest_database.save_doc measurement.reload._document.merge('value' => 2)
16
16
 
17
- measurement.is_dirty
18
17
  db.save measurement do |m|
19
18
  m.value += 1
20
19
  end
21
20
 
22
- expect(measurement.reload.value).to eql(3)
21
+ expect(measurement.value).to eql(3)
23
22
  end
24
23
 
25
24
  it 'raises an error after 5 tries' do
@@ -93,10 +93,10 @@ describe 'properties' do
93
93
 
94
94
  it "should persist a big decimal" do
95
95
  require 'bigdecimal'
96
- c = BigDecimalContainer.new :number => BigDecimal.new( '42.42' )
96
+ c = BigDecimalContainer.new :number => BigDecimal( '42.42' )
97
97
  CouchPotato.database.save_document! c
98
98
  c = CouchPotato.database.load_document c.id
99
- expect(c.number).to eq(BigDecimal.new( '42.42' ))
99
+ expect(c.number).to eq(BigDecimal( '42.42' ))
100
100
  end
101
101
 
102
102
  it "should persist a hash" do
@@ -114,12 +114,21 @@ describe 'properties' do
114
114
  end
115
115
 
116
116
  it "should persist subclasses of the specified type" do
117
- w = Watch.new(:custom_address => [Address2.new])
117
+ w = Watch.new(:custom_address => [Address2.new(id: 'a1', city: 'Berlin')])
118
118
  CouchPotato.database.save_document! w
119
119
  w = CouchPotato.database.load_document w.id
120
+
120
121
  expect(w.custom_address[0]).to be_an_instance_of Address2
121
122
  end
122
123
 
124
+ it 'initializes an typed array property from an array of hashes' do
125
+ w = Watch.new(custom_address: [{id: 'a1', city: 'Berlin'}])
126
+
127
+ expect(w.custom_address.map(&:class)).to eq([Address])
128
+ expect(w.custom_address.map(&:id)).to eq(['a1'])
129
+ expect(w.custom_address.map(&:city)).to eq(['Berlin'])
130
+ end
131
+
123
132
  def it_should_persist value
124
133
  c = Comment.new :title => value
125
134
  CouchPotato.database.save_document! c
data/spec/railtie_spec.rb CHANGED
@@ -83,6 +83,16 @@ describe "railtie" do
83
83
  end
84
84
  end
85
85
 
86
+ context 'yaml file contains additional_databases' do
87
+ it 'assigns additional_databases to config' do
88
+ allow(File).to receive_messages(:read => "test:\n database: test\n additional_databases:\n db2: test2")
89
+
90
+ expect(CouchPotato::Config).to receive(:additional_databases=).with({'db2' => 'test2'})
91
+
92
+ CouchPotato.rails_init
93
+ end
94
+ end
95
+
86
96
  it "should process the yml file with erb" do
87
97
  allow(File).to receive_messages(:read => "test: \n database: <%= 'db' %>")
88
98
 
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rubygems'
2
4
  require 'rspec'
3
5
  require 'time'
4
6
  require 'active_support'
5
7
  require 'timecop'
6
8
 
7
- $:.unshift(File.dirname(__FILE__) + '/../lib')
9
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
8
10
 
9
11
  require 'couch_potato'
10
12
 
@@ -13,7 +15,7 @@ CouchPotato::Config.database_name = ENV['DATABASE'] || 'couch_potato_test'
13
15
  # silence deprecation warnings from ActiveModel as the Spec uses Errors#on
14
16
  begin
15
17
  ActiveSupport::Deprecation.silenced = true
16
- rescue
18
+ rescue StandardError
17
19
  # ignore errors, ActiveSupport is probably not installed
18
20
  end
19
21
 
@@ -64,5 +66,4 @@ RSpec::Matchers.define :eql_ignoring_indentation do |expected|
64
66
  def strip_indentation(string)
65
67
  string.gsub(/^\s+/m, '')
66
68
  end
67
-
68
69
  end
@@ -12,8 +12,12 @@ begin
12
12
  end
13
13
  end
14
14
 
15
- def assert_equal(one, other)
16
- expect(one).to equal(other)
15
+ def assert_equal(one, other, _message = nil)
16
+ expect(one).to eq(other)
17
+ end
18
+
19
+ def assert_respond_to(receiver, method)
20
+ expect(receiver).to respond_to(method)
17
21
  end
18
22
 
19
23
  class ActiveComment
@@ -62,7 +66,7 @@ begin
62
66
  describe "#errors" do
63
67
  it "should return a single error as array" do
64
68
  @model.valid?
65
- expect(@model.errors[:name]).to be_kind_of(Array)
69
+ expect(@model.errors[:name]).to eq(["can't be blank"])
66
70
  end
67
71
 
68
72
  it "should return multiple errors as array" do
@@ -10,7 +10,8 @@ class Plant
10
10
  include CouchPotato::Persistence
11
11
  property :leaf_count
12
12
  property :typed_leaf_count, type: Fixnum
13
- property :typed_leaf_size, type: Float
13
+ property :integer_something, type: Integer
14
+ property :typed_leaf_size, type: Float
14
15
  property :branch, type: Branch
15
16
  end
16
17
 
@@ -40,6 +41,7 @@ describe 'attributes' do
40
41
  plant = Plant.new(leaf_count: 1)
41
42
 
42
43
  expect(plant.attributes).to eq('leaf_count' => 1, 'created_at' => nil,
44
+ 'integer_something' => nil,
43
45
  'updated_at' => nil, 'typed_leaf_count' => nil,
44
46
  'typed_leaf_size' => nil, 'branch' => nil)
45
47
  end
@@ -109,6 +111,57 @@ describe 'attributes' do
109
111
  end
110
112
  end
111
113
 
114
+ describe 'integer' do
115
+ it 'rounds a float to a fixnum' do
116
+ @plant.integer_something = 4.5
117
+
118
+ expect(@plant.integer_something).to eq(5)
119
+ end
120
+
121
+ it 'converts a string into a fixnum' do
122
+ @plant.integer_something = '4'
123
+
124
+ expect(@plant.integer_something).to eq(4)
125
+ end
126
+
127
+ it 'converts a string into a negative fixnum' do
128
+ @plant.integer_something = '-4'
129
+
130
+ expect(@plant.integer_something).to eq(-4)
131
+ end
132
+
133
+ it 'leaves a fixnum as is' do
134
+ @plant.integer_something = 4
135
+
136
+ expect(@plant.integer_something).to eq(4)
137
+ end
138
+
139
+ it 'leaves nil as is' do
140
+ @plant.integer_something = nil
141
+
142
+ expect(@plant.integer_something).to be_nil
143
+ end
144
+
145
+ it 'sets the attributes to zero if a string given' do
146
+ @plant.integer_something = 'x'
147
+
148
+ expect(@plant.integer_something).to eq(0)
149
+ end
150
+
151
+ it 'parses numbers out of a string' do
152
+ @plant.integer_something = 'x123'
153
+
154
+ expect(@plant.integer_something).to eq(123)
155
+ end
156
+
157
+ it 'sets the attributes to nil if given a blank string' do
158
+ @plant.integer_something = ''
159
+
160
+ expect(@plant.integer_something).to be_nil
161
+ end
162
+ end
163
+
164
+
112
165
  describe 'fixnum' do
113
166
  it 'rounds a float to a fixnum' do
114
167
  @plant.typed_leaf_count = 4.5