command_model 1.3.0 → 2.0.0

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