couch_potato 1.7.0 → 1.10.1

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