command_model 1.3.0 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8af8e3bf9074e89aafc9b7e880eb7c99780a9189f822a6a030c8867a3429b0c7
4
- data.tar.gz: 225ab899fa949b25dcbda27e9dac739227f8992d68ea0109afa99e36726f08bd
3
+ metadata.gz: 9e62322d3750507b2218d0e2662517d807c4207a5f225ee98ab9eda6d39a9206
4
+ data.tar.gz: 5350099e5f9e77a68441faf7fab88fdcc736c36a0d1ac9dbd9d48ca75785cf14
5
5
  SHA512:
6
- metadata.gz: 1d01d13237af6fda7df4e9002a71fb029558bd1bc114dc7bdfa1f54f288f80fdf76e744550f0a49292f2bd94fd9f13cf9c65870ab0be75af23f041ff148ae8a3
7
- data.tar.gz: 43a0c62d7d985aff56a7875c84eafb862e9b92e40e0728ae67e468021590e5e4cdb3f6454d05cd27647432312e5bb197ed259d7b0c640f602715b1d0d7ff801b
6
+ metadata.gz: 2aede9435d4d3a0dc779101807f02def57e15764b352d2228a89aa91fc341c098fcf17919f85ec2a9e222a97535b0c9e2a540dc2a104de72262d3f875e1b65c0
7
+ data.tar.gz: 7a01b95bf05e04b86c4735d21b1dae876e34dc4de3f1e6117c166dbeb46c191d7f05df10889791ad60be0355134edd2866598c02d80962984afdeab8891056b9
data/.travis.yml CHANGED
@@ -1,8 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.5.0
4
- - 2.4.3
5
4
  gemfile:
6
- - gemfiles/4.2.gemfile
7
5
  - gemfiles/5.0.gemfile
8
6
  - gemfiles/5.1.gemfile
data/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  # CommandModel
4
4
 
5
5
  CommandModel is an ActiveModel based class that encapsulates the user
6
- interaction logic that wraps a domain operation. This user interaction
7
- typically may include sanitizing, validating, normalizing, and typecasting
8
- input. It also will include the response from the domain operation.
6
+ interaction logic that wraps a domain operation. This user interaction typically
7
+ may include sanitizing, validating, normalizing, and type converting input. It
8
+ also will include the response from the domain operation.
9
9
 
10
10
  There are three major concerns when handling a user request: input handling,
11
11
  domain logic, and persistence. ActiveRecord mixes all three of these concerns
@@ -21,7 +21,7 @@ ActiveRecord style update_attributes.
21
21
  account.withdraw amount: 50
22
22
 
23
23
  But there are multiple complications with the OO approach. How do we integrate
24
- Rails style validations? How are user-supplied strings typecast? How do we
24
+ Rails style validations? How are user-supplied strings type converted? How do we
25
25
  know if the command succeeded? CommandModel solves these problems.
26
26
 
27
27
  ## Installation
@@ -45,7 +45,7 @@ request.
45
45
 
46
46
  class WithdrawCommand < CommandModel::Model
47
47
  parameter :amount,
48
- typecast: :integer,
48
+ convert: :integer,
49
49
  presence: true,
50
50
  numericality: { greater_than: 0, less_than_or_equal_to: 500 }
51
51
  end
@@ -95,7 +95,7 @@ internal domain logic.
95
95
 
96
96
  class WithdrawCommand < CommandModel::Model
97
97
  parameter :amount,
98
- typecast: :integer,
98
+ convert: :integer,
99
99
  presence: true,
100
100
  numericality: { greater_than: 0, less_than_or_equal_to: 500 }
101
101
  parameter :account_id, presence: true
@@ -130,6 +130,12 @@ integration of Rails form helpers and validations with CommandModel.
130
130
 
131
131
  ## Version History
132
132
 
133
+ * 2.0 - April 11, 2018
134
+ * Rename typecast parameter option to convert
135
+ * Any callable object can be used as a type converter
136
+ * Multiple type converters can be chained together
137
+ * Added StringMutator type converter
138
+ * Add boolean type conversion
133
139
  * 1.3 - February 13, 2018
134
140
  * Add decimal type cast
135
141
  * 1.2 - October 24, 2014
@@ -15,12 +15,10 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = CommandModel::VERSION
17
17
 
18
- gem.add_dependency 'activemodel', "> 4.2"
18
+ gem.required_ruby_version = '>= 2.5.0'
19
19
 
20
- gem.add_development_dependency 'rake', "~> 11.3.0"
21
- gem.add_development_dependency 'rspec', "~> 2.14.1"
22
- gem.add_development_dependency 'guard', "~> 2.14.2"
23
- gem.add_development_dependency 'guard-rspec', "~> 3.1.0"
24
- gem.add_development_dependency 'rb-fsevent', '~> 0.10.2'
20
+ gem.add_dependency 'activemodel', "> 5.0"
25
21
 
22
+ gem.add_development_dependency 'rake', "~> 12.3.0"
23
+ gem.add_development_dependency 'rspec', "~> 3.7.0"
26
24
  end
data/lib/command_model.rb CHANGED
@@ -2,6 +2,7 @@ require "active_model"
2
2
 
3
3
  require "command_model/version"
4
4
  require "command_model/model"
5
+ require "command_model/convert"
5
6
 
6
7
  module CommandModel
7
8
  end
@@ -0,0 +1,85 @@
1
+ require 'bigdecimal'
2
+ require 'date'
3
+
4
+ module CommandModel
5
+ module Convert
6
+ class ConvertError < StandardError
7
+ attr_reader :original_error, :target_type
8
+
9
+ def initialize(original_error, target_type)
10
+ @original_error = original_error
11
+ @target_type = target_type
12
+ end
13
+ end
14
+
15
+ class StringMutator
16
+ def initialize(force_to_s=false, &block)
17
+ @force_to_s = force_to_s
18
+ @mutator = block
19
+ end
20
+
21
+ def call(value)
22
+ if @force_to_s
23
+ @mutator.call value.to_s
24
+ elsif value.respond_to? :to_str
25
+ @mutator.call value.to_str
26
+ else
27
+ value
28
+ end
29
+ end
30
+ end
31
+
32
+ class Integer
33
+ def call(value)
34
+ return nil if value.blank?
35
+ Integer(value)
36
+ rescue StandardError => e
37
+ raise ConvertError.new(e, "integer")
38
+ end
39
+ end
40
+
41
+ class Decimal
42
+ def call(value)
43
+ return nil if value.blank?
44
+ BigDecimal(value, 16)
45
+ rescue StandardError => e
46
+ raise ConvertError.new(e, "number")
47
+ end
48
+ end
49
+
50
+ class Float
51
+ def call(value)
52
+ return nil if value.blank?
53
+ Float(value)
54
+ rescue StandardError => e
55
+ raise ConvertError.new(e, "number")
56
+ end
57
+ end
58
+
59
+ class Date
60
+ def call(value)
61
+ return nil if value.blank?
62
+ return value if value.kind_of? Date
63
+ value = value.to_s
64
+ if value =~ /\A(\d\d\d\d)-(\d\d)-(\d\d)\z/
65
+ ::Date.civil($1.to_i, $2.to_i, $3.to_i)
66
+ else
67
+ ::Date.strptime(value, "%m/%d/%Y")
68
+ end
69
+ rescue StandardError => e
70
+ raise ConvertError.new(e, "date")
71
+ end
72
+ end
73
+
74
+ class Boolean
75
+ def call(value)
76
+ case value
77
+ when "", "0", "false", "f", 0
78
+ then false
79
+ else
80
+ !!value
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,45 +1,54 @@
1
1
  module CommandModel
2
+ class TypecastError < StandardError
3
+ attr_reader :original_error
4
+
5
+ def initialize(original_error)
6
+ @original_error = original_error
7
+ end
8
+ end
9
+
2
10
  class Model
3
11
  include ActiveModel::Validations
4
12
  include ActiveModel::Conversion
5
13
  extend ActiveModel::Naming
6
14
 
7
- Parameter = Struct.new(:name, :typecast, :validations)
15
+ Parameter = Struct.new(:name, :converters, :validations)
8
16
 
9
17
  # Parameter requires one or more attributes as its first parameter(s).
10
18
  # It accepts an options hash as its last parameter.
11
19
  #
12
20
  # ==== Options
13
21
  #
14
- # * typecast - The type of object to typecast to. Typecasts are built-in
15
- # for integer, float, and date. Additional typecasts can be defined
16
- # by defining a method typecast_#{name} for a typecast of #{name}.
22
+ # * convert - An object or array of objects that respond to call and
23
+ # convert the assigned value as necessary. Built-in converters exist
24
+ # for integer, decimal, float, date, and boolean. These built-in
25
+ # converters can be specified by symbol.
17
26
  # * validations - All other options are considered validations and are
18
27
  # passed to ActiveModel::Validates.validates
19
28
  #
20
29
  # ==== Examples
21
30
  #
22
31
  # parameter :gender
23
- # parameter :name, :presence => true
24
- # parameter :birthdate, :typecast => :date
32
+ # parameter :name, presence: true
33
+ # parameter :birthdate, convert: :date
25
34
  # parameter :height, :weight,
26
- # :typecast => :integer,
27
- # :presence => true,
28
- # :numericality => { :greater_than_or_equal_to => 0 }
35
+ # convert: [CommandModel::Convert::StringMutator.new { |s| s.gsub(",", "")}, :integer],
36
+ # presence: true,
37
+ # numericality: { :greater_than_or_equal_to => 0 }
29
38
  def self.parameter(*args)
30
39
  options = args.last.kind_of?(Hash) ? args.pop.clone : {}
31
- typecast = options.delete(:typecast)
40
+ converters = options.delete(:convert)
32
41
 
33
42
  args.each do |name|
34
43
  attr_reader name
35
44
 
36
- if typecast
37
- attr_typecasting_writer name, typecast
45
+ if converters
46
+ attr_type_converting_writer name, Array(converters)
38
47
  else
39
48
  attr_writer name
40
49
  end
41
50
  validates name, options.clone if options.present? # clone options because validates mutates the hash :(
42
- parameters.push Parameter.new name, typecast, options
51
+ parameters.push Parameter.new name, converters, options
43
52
  end
44
53
  end
45
54
 
@@ -48,21 +57,37 @@ module CommandModel
48
57
  @parameters ||= []
49
58
  end
50
59
 
51
- def self.attr_typecasting_writer(name, target_type) #:nodoc
52
- eval <<-END_EVAL
53
- public def #{name}=(value)
54
- typecast_value = typecast_#{target_type}(value)
55
- if typecast_value
56
- @typecast_errors.delete("#{name}")
57
- @#{name} = typecast_value
60
+ def self.attr_type_converting_writer(name, converters) #:nodoc
61
+ converters = converters.map do |c|
62
+ if c.respond_to? :call
63
+ c
64
+ else
65
+ case c.to_s
66
+ when "integer"
67
+ CommandModel::Convert::Integer.new
68
+ when "decimal"
69
+ CommandModel::Convert::Decimal.new
70
+ when "float"
71
+ CommandModel::Convert::Float.new
72
+ when "date"
73
+ CommandModel::Convert::Date.new
74
+ when "boolean"
75
+ CommandModel::Convert::Boolean.new
58
76
  else
59
- @typecast_errors["#{name}"] = "#{target_type}"
60
- @#{name} = value
77
+ raise ArgumentError, "unknown converter #{c}"
61
78
  end
62
-
63
- @#{name}
64
79
  end
65
- END_EVAL
80
+ end
81
+
82
+ define_method "#{name}=" do |value|
83
+ converted_value = converters.reduce(value) { |v, c| c.call(v) }
84
+ instance_variable_set "@#{name}", converted_value
85
+ instance_variable_get("@type_conversion_errors").delete(name)
86
+ instance_variable_get "@#{name}"
87
+ rescue CommandModel::Convert::ConvertError => e
88
+ instance_variable_get("@type_conversion_errors")[name] = e.target_type
89
+ instance_variable_set "@#{name}", value
90
+ end
66
91
  end
67
92
 
68
93
  # Executes a block of code if the command model is valid.
@@ -113,7 +138,7 @@ module CommandModel
113
138
  # instance of the same class is passed in then the parameters are copied
114
139
  # to the new object.
115
140
  def initialize(parameters={})
116
- @typecast_errors = {}
141
+ @type_conversion_errors = {}
117
142
  set_parameters parameters
118
143
  end
119
144
 
@@ -179,30 +204,8 @@ module CommandModel
179
204
  end
180
205
  end
181
206
 
182
- def typecast_integer(value)
183
- Integer(value) rescue nil
184
- end
185
-
186
- def typecast_decimal(value)
187
- BigDecimal(value, 16) rescue nil
188
- end
189
-
190
- def typecast_float(value)
191
- Float(value) rescue nil
192
- end
193
-
194
- def typecast_date(value)
195
- return value if value.kind_of? Date
196
- value = value.to_s
197
- if value =~ /\A(\d\d\d\d)-(\d\d)-(\d\d)\z/
198
- Date.civil($1.to_i, $2.to_i, $3.to_i) rescue nil
199
- else
200
- Date.strptime(value, "%m/%d/%Y") rescue nil
201
- end
202
- end
203
-
204
207
  def include_typecasting_errors
205
- @typecast_errors.each do |attribute, target_type|
208
+ @type_conversion_errors.each do |attribute, target_type|
206
209
  unless errors[attribute].present?
207
210
  errors.add attribute, "is not a #{target_type}"
208
211
  end
@@ -1,3 +1,3 @@
1
1
  module CommandModel
2
- VERSION = "1.3.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe "CommandModel::Convert" do
4
+ describe "StringMutator" do
5
+ subject { CommandModel::Convert::StringMutator.new { |s| s.gsub(",", "") } }
6
+
7
+ it "strips commas from strings" do
8
+ expect(subject.("1,000")).to eq("1000")
9
+ end
10
+
11
+ it "does nothing to non-strings" do
12
+ expect(subject.(nil)).to eq(nil)
13
+ expect(subject.(42)).to eq(42)
14
+ expect(subject.(:bla)).to eq(:bla)
15
+ end
16
+ end
17
+
18
+ describe "Integer" do
19
+ subject { CommandModel::Convert::Integer.new }
20
+
21
+ it "casts to integer when valid string" do
22
+ expect(subject.("42")).to eq(42)
23
+ end
24
+
25
+ it "accepts nil" do
26
+ expect(subject.(nil)).to eq(nil)
27
+ end
28
+
29
+ it "converts empty string to nil" do
30
+ expect(subject.("")).to eq(nil)
31
+ end
32
+
33
+ it "raises TypecastError when invalid string" do
34
+ expect { subject.("asdf") }.to raise_error(CommandModel::Convert::ConvertError)
35
+ expect { subject.("0.1") }.to raise_error(CommandModel::Convert::ConvertError)
36
+ end
37
+ end
38
+
39
+ describe "Float" do
40
+ subject { CommandModel::Convert::Float.new }
41
+
42
+ it "casts to float when valid string" do
43
+ expect(subject.("42")).to eq(42.0)
44
+ expect(subject.("42.5")).to eq(42.5)
45
+ end
46
+
47
+ it "accepts nil" do
48
+ expect(subject.(nil)).to eq(nil)
49
+ end
50
+
51
+ it "converts empty string to nil" do
52
+ expect(subject.("")).to eq(nil)
53
+ end
54
+
55
+ it "raises TypecastError when invalid string" do
56
+ expect { subject.("asdf") }.to raise_error(CommandModel::Convert::ConvertError)
57
+ end
58
+ end
59
+
60
+ describe "Decimal" do
61
+ subject { CommandModel::Convert::Decimal.new }
62
+
63
+ it "converts to BigDecimal when valid string" do
64
+ expect(subject.("42")).to eq(BigDecimal("42"))
65
+ expect(subject.("42.5")).to eq(BigDecimal("42.5"))
66
+ end
67
+
68
+ it "converts to BigDecimal when float" do
69
+ expect(subject.(42.0)).to eq(BigDecimal("42"))
70
+ end
71
+
72
+ it "converts to BigDecimal when int" do
73
+ expect(subject.(42)).to eq(BigDecimal("42"))
74
+ end
75
+
76
+ it "accepts nil" do
77
+ expect(subject.(nil)).to eq(nil)
78
+ end
79
+
80
+ it "converts empty string to nil" do
81
+ expect(subject.("")).to eq(nil)
82
+ end
83
+
84
+ it "raises TypecastError when invalid string" do
85
+ expect { subject.("asdf") }.to raise_error(CommandModel::Convert::ConvertError)
86
+ end
87
+ end
88
+
89
+ describe "Date" do
90
+ subject { CommandModel::Convert::Date.new }
91
+
92
+ it "casts to date when valid string" do
93
+ expect(subject.("01/01/2000")).to eq(Date.civil(2000,1,1))
94
+ expect(subject.("1/1/2000")).to eq(Date.civil(2000,1,1))
95
+ expect(subject.("2000-01-01")).to eq(Date.civil(2000,1,1))
96
+ end
97
+
98
+ it "returns existing date unchanged" do
99
+ date = Date.civil(2000,1,1)
100
+ expect(subject.(date)).to eq(date)
101
+ end
102
+
103
+ it "accepts nil" do
104
+ expect(subject.(nil)).to eq(nil)
105
+ end
106
+
107
+ it "converts empty string to nil" do
108
+ expect(subject.("")).to eq(nil)
109
+ end
110
+
111
+ it "raises TypecastError when invalid string" do
112
+ expect { subject.("asdf") }.to raise_error(CommandModel::Convert::ConvertError)
113
+ expect { subject.("3/50/1290") }.to raise_error(CommandModel::Convert::ConvertError)
114
+ end
115
+ end
116
+
117
+ describe "Boolean" do
118
+ subject { CommandModel::Convert::Boolean.new }
119
+
120
+ it "casts to true when any non-false value" do
121
+ expect(subject.("true")).to eq(true)
122
+ expect(subject.("t")).to eq(true)
123
+ expect(subject.("1")).to eq(true)
124
+ expect(subject.(true)).to eq(true)
125
+ expect(subject.(Object.new)).to eq(true)
126
+ expect(subject.(42)).to eq(true)
127
+ end
128
+
129
+ it "casts to false when false values" do
130
+ expect(subject.("")).to eq(false)
131
+ expect(subject.("0")).to eq(false)
132
+ expect(subject.("f")).to eq(false)
133
+ expect(subject.("false")).to eq(false)
134
+ expect(subject.(0)).to eq(false)
135
+ expect(subject.(nil)).to eq(false)
136
+ expect(subject.(false)).to eq(false)
137
+ end
138
+ end
139
+ end
data/spec/model_spec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class ExampleCommand < CommandModel::Model
4
- parameter :name, :presence => true
4
+ parameter :name, presence: true
5
5
  parameter :title
6
6
  end
7
7
 
@@ -14,57 +14,63 @@ describe CommandModel::Model do
14
14
 
15
15
  it "creates an attribute reader" do
16
16
  klass.parameter :foo
17
- klass.new.methods.should include(:foo)
17
+ expect(klass.new.methods).to include(:foo)
18
18
  end
19
19
 
20
20
  it "creates an attribute writer" do
21
21
  klass.parameter :foo
22
- klass.new.methods.should include(:foo=)
22
+ expect(klass.new.methods).to include(:foo=)
23
23
  end
24
24
 
25
25
  it "round trips values through writing and reading" do
26
26
  klass.parameter :foo
27
27
  instance = klass.new
28
28
  instance.foo = 42
29
- instance.foo.should eq(42)
29
+ expect(instance.foo).to eq(42)
30
30
  end
31
31
 
32
32
  it "accepts multiple attributes" do
33
33
  klass.parameter :foo, :bar
34
- klass.new.methods.should include(:foo)
35
- klass.new.methods.should include(:foo=)
36
- klass.new.methods.should include(:bar)
37
- klass.new.methods.should include(:bar=)
34
+ expect(klass.new.methods).to include(:foo)
35
+ expect(klass.new.methods).to include(:foo=)
36
+ expect(klass.new.methods).to include(:bar)
37
+ expect(klass.new.methods).to include(:bar=)
38
38
  end
39
39
 
40
- it "accepts multiple attributes with typecast" do
41
- klass.parameter :foo, :bar, :typecast => "integer"
42
- klass.new.methods.should include(:foo)
43
- klass.new.methods.should include(:foo=)
44
- klass.new.methods.should include(:bar)
45
- klass.new.methods.should include(:bar=)
40
+ it "accepts multiple attributes with convert" do
41
+ klass.parameter :foo, :bar, :convert => :integer
42
+ expect(klass.new.methods).to include(:foo)
43
+ expect(klass.new.methods).to include(:foo=)
44
+ expect(klass.new.methods).to include(:bar)
45
+ expect(klass.new.methods).to include(:bar=)
46
46
  end
47
47
 
48
48
  it "accepts multiple attributes with validation" do
49
49
  klass.parameter :foo, :bar, :presence => true
50
- klass.new.methods.should include(:foo)
51
- klass.new.methods.should include(:foo=)
52
- klass.new.methods.should include(:bar)
53
- klass.new.methods.should include(:bar=)
50
+ expect(klass.new.methods).to include(:foo)
51
+ expect(klass.new.methods).to include(:foo=)
52
+ expect(klass.new.methods).to include(:bar)
53
+ expect(klass.new.methods).to include(:bar=)
54
54
  end
55
55
 
56
- it "creates typecasting writer" do
57
- klass.send(:define_method, :typecast_42) { |value| 42 }
58
- klass.parameter :answer, :typecast => "42"
56
+ it "converts via callable" do
57
+ klass.parameter :answer, convert: ->(value) { 42 }
59
58
  instance = klass.new
60
59
  instance.answer = "foo"
61
- instance.answer.should eq(42)
60
+ expect(instance.answer).to eq(42)
61
+ end
62
+
63
+ it "converts with multiple converters" do
64
+ klass.parameter :num, convert: [CommandModel::Convert::StringMutator.new { |s| s.gsub(",", "")}, :integer]
65
+ instance = klass.new
66
+ instance.num = "1,000"
67
+ expect(instance.num).to eq(1000)
62
68
  end
63
69
 
64
70
  it "creates validations" do
65
71
  instance = ExampleCommand.new
66
- instance.should_not be_valid
67
- instance.errors[:name].should be_present
72
+ expect(instance).to_not be_valid
73
+ expect(instance.errors[:name]).to be_present
68
74
  end
69
75
  end
70
76
 
@@ -72,7 +78,7 @@ describe CommandModel::Model do
72
78
  it "returns all parameters in class" do
73
79
  klass = Class.new(CommandModel::Model)
74
80
  klass.parameter :name, presence: true
75
- klass.parameter :birthdate, typecast: :date, presence: true
81
+ klass.parameter :birthdate, convert: :date, presence: true
76
82
 
77
83
  expected = [
78
84
  CommandModel::Model::Parameter.new(:name, nil, { presence: true }),
@@ -85,35 +91,35 @@ describe CommandModel::Model do
85
91
 
86
92
  describe "self.execute" do
87
93
  it "accepts object of same kind and returns it" do
88
- ExampleCommand.execute(example_command) {}.should eq(example_command)
94
+ expect(ExampleCommand.execute(example_command) {}).to eq(example_command)
89
95
  end
90
96
 
91
97
  it "accepts attributes, creates object, and returns it" do
92
98
  c = ExampleCommand.execute(:name => "John") {}
93
- c.should be_kind_of(ExampleCommand)
94
- c.name.should eq("John")
99
+ expect(c).to be_kind_of(ExampleCommand)
100
+ expect(c.name).to eq("John")
95
101
  end
96
102
 
97
103
  it "calls passed block when there are no validation errors on Model" do
98
104
  block_ran = false
99
105
  ExampleCommand.execute(example_command) { block_ran = true }
100
- block_ran.should eq(true)
106
+ expect(block_ran).to eq(true)
101
107
  end
102
108
 
103
109
  it "does not call passed block when there are validation errors on Model" do
104
110
  block_ran = false
105
111
  ExampleCommand.execute(invalid_example_command) { block_ran = true }
106
- block_ran.should eq(false)
112
+ expect(block_ran).to eq(false)
107
113
  end
108
114
 
109
115
  it "records execution attempt when there not no validation errors on Model" do
110
116
  ExampleCommand.execute(example_command) {}
111
- example_command.execution_attempted?.should eq(true)
117
+ expect(example_command.execution_attempted?).to eq(true)
112
118
  end
113
119
 
114
120
  it "records execution attempt when there are validation errors on Model" do
115
121
  ExampleCommand.execute(invalid_example_command) {}
116
- invalid_example_command.execution_attempted?.should eq(true)
122
+ expect(invalid_example_command.execution_attempted?).to eq(true)
117
123
  end
118
124
 
119
125
  it "is not successful if block adds error to Model" do
@@ -121,24 +127,24 @@ describe CommandModel::Model do
121
127
  command.errors.add :base, "foo"
122
128
  end
123
129
 
124
- example_command.should_not be_success
130
+ expect(example_command).to_not be_success
125
131
  end
126
132
  end
127
133
 
128
134
  describe "self.success" do
129
135
  it "creates a successful command model" do
130
136
  response = ExampleCommand.success
131
- response.should be_kind_of(ExampleCommand)
132
- response.should be_success
137
+ expect(response).to be_kind_of(ExampleCommand)
138
+ expect(response).to be_success
133
139
  end
134
140
  end
135
141
 
136
142
  describe "self.failure" do
137
143
  it "creates a command model with an error" do
138
144
  response = ExampleCommand.failure "something broke"
139
- response.should be_kind_of(ExampleCommand)
140
- response.should_not be_success
141
- response.errors[:base].should eq(["something broke"])
145
+ expect(response).to be_kind_of(ExampleCommand)
146
+ expect(response).to_not be_success
147
+ expect(response.errors[:base]).to eq(["something broke"])
142
148
  end
143
149
  end
144
150
 
@@ -146,7 +152,7 @@ describe CommandModel::Model do
146
152
  describe "initialize" do
147
153
  it "assigns parameters from hash" do
148
154
  m = ExampleCommand.new :name => "John"
149
- m.name.should eq("John")
155
+ expect(m.name).to eq("John")
150
156
  end
151
157
 
152
158
  it "assigns parameters from other CommandModel" do
@@ -159,7 +165,7 @@ describe CommandModel::Model do
159
165
  describe "call" do
160
166
  context "when valid" do
161
167
  it "calls execute" do
162
- example_command.should_receive(:execute)
168
+ expect(example_command).to receive(:execute)
163
169
  example_command.call
164
170
  end
165
171
 
@@ -170,7 +176,7 @@ describe CommandModel::Model do
170
176
 
171
177
  context "when invalid" do
172
178
  it "does not call execute" do
173
- invalid_example_command.should_not_receive(:execute)
179
+ expect(invalid_example_command).to_not receive(:execute)
174
180
  invalid_example_command.call
175
181
  end
176
182
 
@@ -194,24 +200,24 @@ describe CommandModel::Model do
194
200
  describe "execution_attempted!" do
195
201
  it "sets execution_attempted? to true" do
196
202
  example_command.execution_attempted!
197
- example_command.execution_attempted?.should eq(true)
203
+ expect(example_command.execution_attempted?).to eq(true)
198
204
  end
199
205
  end
200
206
 
201
207
  describe "success?" do
202
208
  it "is false before execution" do
203
- example_command.should_not be_success
209
+ expect(example_command).to_not be_success
204
210
  end
205
211
 
206
212
  it "is false after execution with errors" do
207
213
  example_command.execution_attempted!
208
214
  example_command.errors.add :base, "foo"
209
- example_command.success?.should eq(false)
215
+ expect(example_command.success?).to eq(false)
210
216
  end
211
217
 
212
218
  it "is true after execution without errors" do
213
219
  example_command.execution_attempted!
214
- example_command.success?.should eq(true)
220
+ expect(example_command.success?).to eq(true)
215
221
  end
216
222
  end
217
223
 
@@ -219,7 +225,7 @@ describe CommandModel::Model do
219
225
  it "is a hash of all parameter name and values" do
220
226
  klass = Class.new(CommandModel::Model)
221
227
  klass.parameter :name, presence: true
222
- klass.parameter :birthdate, typecast: :date, presence: true
228
+ klass.parameter :birthdate, convert: :date, presence: true
223
229
 
224
230
  expected = { name: "John", birthdate: Date.new(1980,1,1) }
225
231
  instance = klass.new expected
@@ -241,85 +247,16 @@ describe CommandModel::Model do
241
247
  end
242
248
  end
243
249
 
244
- describe "typecast_integer" do
245
- it "casts to integer when valid string" do
246
- example_command.send(:typecast_integer, "42").should eq(42)
247
- end
248
-
249
- it "returns nil when invalid string" do
250
- example_command.send(:typecast_integer, "asdf").should be_nil
251
- example_command.send(:typecast_integer, nil).should be_nil
252
- example_command.send(:typecast_integer, "").should be_nil
253
- example_command.send(:typecast_integer, "0.1").should be_nil
254
- end
250
+ it "includes type conversion errors in validations" do
251
+ example_command.instance_variable_get(:@type_conversion_errors)["name"] = "integer"
252
+ expect(example_command).to_not be_valid
253
+ expect(example_command.errors["name"]).to be
255
254
  end
256
255
 
257
- describe "typecast_float" do
258
- it "casts to float when valid string" do
259
- example_command.send(:typecast_float, "42").should eq(42.0)
260
- example_command.send(:typecast_float, "42.5").should eq(42.5)
261
- end
262
-
263
- it "returns nil when invalid string" do
264
- example_command.send(:typecast_float, "asdf").should be_nil
265
- example_command.send(:typecast_float, nil).should be_nil
266
- example_command.send(:typecast_float, "").should be_nil
267
- end
256
+ it "does not include type conversion error in validations if the attribute already has an error" do
257
+ invalid_example_command.instance_variable_get(:@type_conversion_errors)["name"] = "integer"
258
+ expect(invalid_example_command).to_not be_valid
259
+ expect(invalid_example_command.errors["name"]).to be
260
+ expect(invalid_example_command.errors["name"].find { |e| e =~ /integer/ }).to_not be
268
261
  end
269
-
270
- describe "typecast_decimal" do
271
- it "converts to BigDecimal when valid string" do
272
- example_command.send(:typecast_decimal, "42").should eq(BigDecimal("42"))
273
- example_command.send(:typecast_decimal, "42.5").should eq(BigDecimal("42.5"))
274
- end
275
-
276
- it "converts to BigDecimal when float" do
277
- example_command.send(:typecast_decimal, 42.0).should eq(BigDecimal("42"))
278
- end
279
-
280
- it "converts to BigDecimal when int" do
281
- example_command.send(:typecast_decimal, 42).should eq(BigDecimal("42"))
282
- end
283
-
284
- it "returns nil when invalid string" do
285
- example_command.send(:typecast_decimal, "asdf").should be_nil
286
- example_command.send(:typecast_decimal, nil).should be_nil
287
- example_command.send(:typecast_decimal, "").should be_nil
288
- end
289
- end
290
-
291
- describe "typecast_date" do
292
- it "casts to date when valid string" do
293
- example_command.send(:typecast_date, "01/01/2000").should eq(Date.civil(2000,1,1))
294
- example_command.send(:typecast_date, "1/1/2000").should eq(Date.civil(2000,1,1))
295
- example_command.send(:typecast_date, "2000-01-01").should eq(Date.civil(2000,1,1))
296
- end
297
-
298
- it "returns existing date unchanged" do
299
- date = Date.civil(2000,1,1)
300
- example_command.send(:typecast_date, date).should eq(date)
301
- end
302
-
303
- it "returns nil when invalid string" do
304
- example_command.send(:typecast_date, "asdf").should be_nil
305
- example_command.send(:typecast_date, nil).should be_nil
306
- example_command.send(:typecast_date, "").should be_nil
307
- example_command.send(:typecast_date, "3/50/1290").should be_nil
308
- end
309
- end
310
-
311
- it "includes typecasting errors in validations" do
312
- example_command.instance_variable_get(:@typecast_errors)["name"] = "integer"
313
- example_command.should_not be_valid
314
- example_command.errors["name"].should be
315
- end
316
-
317
- it "does not include typecasting error in validations if the attribute already has an error" do
318
- invalid_example_command.instance_variable_get(:@typecast_errors)["name"] = "integer"
319
- invalid_example_command.should_not be_valid
320
- invalid_example_command.errors["name"].should be
321
- invalid_example_command.errors["name"].find { |e| e =~ /integer/ }.should_not be
322
- end
323
-
324
-
325
262
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Christensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-14 00:00:00.000000000 Z
11
+ date: 2018-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -16,84 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - ">"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.2'
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 11.3.0
33
+ version: 12.3.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 11.3.0
40
+ version: 12.3.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 2.14.1
47
+ version: 3.7.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 2.14.1
55
- - !ruby/object:Gem::Dependency
56
- name: guard
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 2.14.2
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 2.14.2
69
- - !ruby/object:Gem::Dependency
70
- name: guard-rspec
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 3.1.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 3.1.0
83
- - !ruby/object:Gem::Dependency
84
- name: rb-fsevent
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: 0.10.2
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: 0.10.2
54
+ version: 3.7.0
97
55
  description: CommandModel - when update_attributes isn't enough.
98
56
  email:
99
57
  - jack@jackchristensen.com
@@ -105,7 +63,6 @@ files:
105
63
  - ".rspec"
106
64
  - ".travis.yml"
107
65
  - Gemfile
108
- - Guardfile
109
66
  - LICENSE
110
67
  - README.md
111
68
  - Rakefile
@@ -170,12 +127,13 @@ files:
170
127
  - examples/bank/vendor/assets/javascripts/.gitkeep
171
128
  - examples/bank/vendor/assets/stylesheets/.gitkeep
172
129
  - examples/bank/vendor/plugins/.gitkeep
173
- - gemfiles/4.2.gemfile
174
130
  - gemfiles/5.0.gemfile
175
131
  - gemfiles/5.1.gemfile
176
132
  - lib/command_model.rb
133
+ - lib/command_model/convert.rb
177
134
  - lib/command_model/model.rb
178
135
  - lib/command_model/version.rb
136
+ - spec/convert_spec.rb
179
137
  - spec/model_spec.rb
180
138
  - spec/spec_helper.rb
181
139
  homepage: https://github.com/JackC/command_model
@@ -189,7 +147,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
147
  requirements:
190
148
  - - ">="
191
149
  - !ruby/object:Gem::Version
192
- version: '0'
150
+ version: 2.5.0
193
151
  required_rubygems_version: !ruby/object:Gem::Requirement
194
152
  requirements:
195
153
  - - ">="
@@ -197,12 +155,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
155
  version: '0'
198
156
  requirements: []
199
157
  rubyforge_project:
200
- rubygems_version: 2.7.3
158
+ rubygems_version: 2.7.6
201
159
  signing_key:
202
160
  specification_version: 4
203
161
  summary: CommandModel integrates Rails validations with command objects. This allows
204
162
  errors from command execution to easily be handled with the familiar Rails validation
205
163
  system.
206
164
  test_files:
165
+ - spec/convert_spec.rb
207
166
  - spec/model_spec.rb
208
167
  - spec/spec_helper.rb
data/Guardfile DELETED
@@ -1,9 +0,0 @@
1
- # A sample Guardfile
2
- # More info at https://github.com/guard/guard#readme
3
-
4
- guard 'rspec', :version => 2 do
5
- watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/command_model/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
8
- end
9
-
data/gemfiles/4.2.gemfile DELETED
@@ -1,5 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "activemodel", "~> 4.2.0"
4
-
5
- gemspec :path=>"../"