command_model 1.3.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8af8e3bf9074e89aafc9b7e880eb7c99780a9189f822a6a030c8867a3429b0c7
4
- data.tar.gz: 225ab899fa949b25dcbda27e9dac739227f8992d68ea0109afa99e36726f08bd
3
+ metadata.gz: 894d328023ecf9f0a8935bfe2a198e2fa3d26a55d42b2a4c0178bfa390e9b4bf
4
+ data.tar.gz: c00d13c0db81fd1a1c9c49e3ff9fa473d62c0c8131ca449acf8f6dd695109a7d
5
5
  SHA512:
6
- metadata.gz: 1d01d13237af6fda7df4e9002a71fb029558bd1bc114dc7bdfa1f54f288f80fdf76e744550f0a49292f2bd94fd9f13cf9c65870ab0be75af23f041ff148ae8a3
7
- data.tar.gz: 43a0c62d7d985aff56a7875c84eafb862e9b92e40e0728ae67e468021590e5e4cdb3f6454d05cd27647432312e5bb197ed259d7b0c640f602715b1d0d7ff801b
6
+ metadata.gz: 016761f5657fd6b00664d0d8e3730d83055506314fa4a49a02194eaa6e7e7f680d8ae234590616a148c31ccc7ab5dfd613b63385121c8e10dafc96adc426cd9b
7
+ data.tar.gz: fc0b8d08827f71c8882697eeb2918f9bde7364aecd7f4796e9125727b73ceb0113022427ff76ed1ce96804c624780cc80be23823587828bcf549b6a0cc852d6b
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.1
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,14 @@ integration of Rails form helpers and validations with CommandModel.
130
130
 
131
131
  ## Version History
132
132
 
133
+ * 2.0.1 - April 3, 2023
134
+ * Date parsing allows 5 digit years
135
+ * 2.0 - April 11, 2018
136
+ * Rename typecast parameter option to convert
137
+ * Any callable object can be used as a type converter
138
+ * Multiple type converters can be chained together
139
+ * Added StringMutator type converter
140
+ * Add boolean type conversion
133
141
  * 1.3 - February 13, 2018
134
142
  * Add decimal type cast
135
143
  * 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
@@ -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{4,5})-(\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
@@ -4,42 +4,43 @@ module CommandModel
4
4
  include ActiveModel::Conversion
5
5
  extend ActiveModel::Naming
6
6
 
7
- Parameter = Struct.new(:name, :typecast, :validations)
7
+ Parameter = Struct.new(:name, :converters, :validations)
8
8
 
9
9
  # Parameter requires one or more attributes as its first parameter(s).
10
10
  # It accepts an options hash as its last parameter.
11
11
  #
12
12
  # ==== Options
13
13
  #
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}.
14
+ # * convert - An object or array of objects that respond to call and
15
+ # convert the assigned value as necessary. Built-in converters exist
16
+ # for integer, decimal, float, date, and boolean. These built-in
17
+ # converters can be specified by symbol.
17
18
  # * validations - All other options are considered validations and are
18
19
  # passed to ActiveModel::Validates.validates
19
20
  #
20
21
  # ==== Examples
21
22
  #
22
23
  # parameter :gender
23
- # parameter :name, :presence => true
24
- # parameter :birthdate, :typecast => :date
24
+ # parameter :name, presence: true
25
+ # parameter :birthdate, convert: :date
25
26
  # parameter :height, :weight,
26
- # :typecast => :integer,
27
- # :presence => true,
28
- # :numericality => { :greater_than_or_equal_to => 0 }
27
+ # convert: [CommandModel::Convert::StringMutator.new { |s| s.gsub(",", "")}, :integer],
28
+ # presence: true,
29
+ # numericality: { :greater_than_or_equal_to => 0 }
29
30
  def self.parameter(*args)
30
31
  options = args.last.kind_of?(Hash) ? args.pop.clone : {}
31
- typecast = options.delete(:typecast)
32
+ converters = options.delete(:convert)
32
33
 
33
34
  args.each do |name|
34
35
  attr_reader name
35
36
 
36
- if typecast
37
- attr_typecasting_writer name, typecast
37
+ if converters
38
+ attr_type_converting_writer name, Array(converters)
38
39
  else
39
40
  attr_writer name
40
41
  end
41
42
  validates name, options.clone if options.present? # clone options because validates mutates the hash :(
42
- parameters.push Parameter.new name, typecast, options
43
+ parameters.push Parameter.new name, converters, options
43
44
  end
44
45
  end
45
46
 
@@ -48,21 +49,37 @@ module CommandModel
48
49
  @parameters ||= []
49
50
  end
50
51
 
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
52
+ def self.attr_type_converting_writer(name, converters) #:nodoc
53
+ converters = converters.map do |c|
54
+ if c.respond_to? :call
55
+ c
56
+ else
57
+ case c.to_s
58
+ when "integer"
59
+ CommandModel::Convert::Integer.new
60
+ when "decimal"
61
+ CommandModel::Convert::Decimal.new
62
+ when "float"
63
+ CommandModel::Convert::Float.new
64
+ when "date"
65
+ CommandModel::Convert::Date.new
66
+ when "boolean"
67
+ CommandModel::Convert::Boolean.new
58
68
  else
59
- @typecast_errors["#{name}"] = "#{target_type}"
60
- @#{name} = value
69
+ raise ArgumentError, "unknown converter #{c}"
61
70
  end
62
-
63
- @#{name}
64
71
  end
65
- END_EVAL
72
+ end
73
+
74
+ define_method "#{name}=" do |value|
75
+ converted_value = converters.reduce(value) { |v, c| c.call(v) }
76
+ instance_variable_set "@#{name}", converted_value
77
+ instance_variable_get("@type_conversion_errors").delete(name)
78
+ instance_variable_get "@#{name}"
79
+ rescue CommandModel::Convert::ConvertError => e
80
+ instance_variable_get("@type_conversion_errors")[name] = e.target_type
81
+ instance_variable_set "@#{name}", value
82
+ end
66
83
  end
67
84
 
68
85
  # Executes a block of code if the command model is valid.
@@ -113,7 +130,7 @@ module CommandModel
113
130
  # instance of the same class is passed in then the parameters are copied
114
131
  # to the new object.
115
132
  def initialize(parameters={})
116
- @typecast_errors = {}
133
+ @type_conversion_errors = {}
117
134
  set_parameters parameters
118
135
  end
119
136
 
@@ -179,30 +196,8 @@ module CommandModel
179
196
  end
180
197
  end
181
198
 
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
199
  def include_typecasting_errors
205
- @typecast_errors.each do |attribute, target_type|
200
+ @type_conversion_errors.each do |attribute, target_type|
206
201
  unless errors[attribute].present?
207
202
  errors.add attribute, "is not a #{target_type}"
208
203
  end
@@ -1,3 +1,3 @@
1
1
  module CommandModel
2
- VERSION = "1.3.0"
2
+ VERSION = "2.0.1"
3
3
  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,140 @@
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 ConvertError 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 ConvertError 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 ConvertError 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
+ expect(subject.("29000-01-01")).to eq(Date.civil(29000,1,1))
97
+ end
98
+
99
+ it "returns existing date unchanged" do
100
+ date = Date.civil(2000,1,1)
101
+ expect(subject.(date)).to eq(date)
102
+ end
103
+
104
+ it "accepts nil" do
105
+ expect(subject.(nil)).to eq(nil)
106
+ end
107
+
108
+ it "converts empty string to nil" do
109
+ expect(subject.("")).to eq(nil)
110
+ end
111
+
112
+ it "raises ConvertError when invalid string" do
113
+ expect { subject.("asdf") }.to raise_error(CommandModel::Convert::ConvertError)
114
+ expect { subject.("3/50/1290") }.to raise_error(CommandModel::Convert::ConvertError)
115
+ end
116
+ end
117
+
118
+ describe "Boolean" do
119
+ subject { CommandModel::Convert::Boolean.new }
120
+
121
+ it "casts to true when any non-false value" do
122
+ expect(subject.("true")).to eq(true)
123
+ expect(subject.("t")).to eq(true)
124
+ expect(subject.("1")).to eq(true)
125
+ expect(subject.(true)).to eq(true)
126
+ expect(subject.(Object.new)).to eq(true)
127
+ expect(subject.(42)).to eq(true)
128
+ end
129
+
130
+ it "casts to false when false values" do
131
+ expect(subject.("")).to eq(false)
132
+ expect(subject.("0")).to eq(false)
133
+ expect(subject.("f")).to eq(false)
134
+ expect(subject.("false")).to eq(false)
135
+ expect(subject.(0)).to eq(false)
136
+ expect(subject.(nil)).to eq(false)
137
+ expect(subject.(false)).to eq(false)
138
+ end
139
+ end
140
+ 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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jack Christensen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-14 00:00:00.000000000 Z
11
+ date: 2023-04-03 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
@@ -103,9 +61,9 @@ extra_rdoc_files: []
103
61
  files:
104
62
  - ".gitignore"
105
63
  - ".rspec"
64
+ - ".ruby-version"
106
65
  - ".travis.yml"
107
66
  - Gemfile
108
- - Guardfile
109
67
  - LICENSE
110
68
  - README.md
111
69
  - Rakefile
@@ -170,18 +128,19 @@ files:
170
128
  - examples/bank/vendor/assets/javascripts/.gitkeep
171
129
  - examples/bank/vendor/assets/stylesheets/.gitkeep
172
130
  - examples/bank/vendor/plugins/.gitkeep
173
- - gemfiles/4.2.gemfile
174
131
  - gemfiles/5.0.gemfile
175
132
  - gemfiles/5.1.gemfile
176
133
  - lib/command_model.rb
134
+ - lib/command_model/convert.rb
177
135
  - lib/command_model/model.rb
178
136
  - lib/command_model/version.rb
137
+ - spec/convert_spec.rb
179
138
  - spec/model_spec.rb
180
139
  - spec/spec_helper.rb
181
140
  homepage: https://github.com/JackC/command_model
182
141
  licenses: []
183
142
  metadata: {}
184
- post_install_message:
143
+ post_install_message:
185
144
  rdoc_options: []
186
145
  require_paths:
187
146
  - lib
@@ -189,20 +148,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
148
  requirements:
190
149
  - - ">="
191
150
  - !ruby/object:Gem::Version
192
- version: '0'
151
+ version: 2.5.0
193
152
  required_rubygems_version: !ruby/object:Gem::Requirement
194
153
  requirements:
195
154
  - - ">="
196
155
  - !ruby/object:Gem::Version
197
156
  version: '0'
198
157
  requirements: []
199
- rubyforge_project:
200
- rubygems_version: 2.7.3
201
- signing_key:
158
+ rubygems_version: 3.4.1
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=>"../"