active_interaction 1.4.1 → 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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -3
  3. data/CONTRIBUTING.md +19 -0
  4. data/README.md +1121 -180
  5. data/lib/active_interaction/backports.rb +58 -13
  6. data/lib/active_interaction/base.rb +16 -52
  7. data/lib/active_interaction/concerns/active_recordable.rb +57 -0
  8. data/lib/active_interaction/concerns/runnable.rb +4 -14
  9. data/lib/active_interaction/errors.rb +14 -66
  10. data/lib/active_interaction/filters/array_filter.rb +12 -9
  11. data/lib/active_interaction/filters/file_filter.rb +5 -24
  12. data/lib/active_interaction/filters/hash_filter.rb +11 -13
  13. data/lib/active_interaction/filters/interface_filter.rb +2 -2
  14. data/lib/active_interaction/filters/{model_filter.rb → object_filter.rb} +6 -6
  15. data/lib/active_interaction/locale/en.yml +1 -1
  16. data/lib/active_interaction/modules/validation.rb +2 -2
  17. data/lib/active_interaction/version.rb +1 -1
  18. data/lib/active_interaction.rb +25 -13
  19. data/spec/active_interaction/base_spec.rb +15 -39
  20. data/spec/active_interaction/concerns/active_recordable_spec.rb +51 -0
  21. data/spec/active_interaction/concerns/runnable_spec.rb +2 -34
  22. data/spec/active_interaction/errors_spec.rb +6 -89
  23. data/spec/active_interaction/filters/array_filter_spec.rb +2 -2
  24. data/spec/active_interaction/filters/file_filter_spec.rb +4 -4
  25. data/spec/active_interaction/filters/hash_filter_spec.rb +1 -17
  26. data/spec/active_interaction/filters/{model_filter_spec.rb → object_filter_spec.rb} +17 -17
  27. data/spec/active_interaction/i18n_spec.rb +1 -2
  28. data/spec/active_interaction/integration/array_interaction_spec.rb +10 -0
  29. data/spec/active_interaction/integration/hash_interaction_spec.rb +12 -2
  30. data/spec/active_interaction/integration/interface_interaction_spec.rb +10 -1
  31. data/spec/active_interaction/integration/object_interaction_spec.rb +16 -0
  32. data/spec/active_interaction/modules/validation_spec.rb +1 -2
  33. metadata +32 -29
  34. data/lib/active_interaction/concerns/transactable.rb +0 -79
  35. data/spec/active_interaction/concerns/transactable_spec.rb +0 -135
  36. data/spec/active_interaction/integration/model_interaction_spec.rb +0 -7
@@ -1,8 +1,7 @@
1
1
  # coding: utf-8
2
2
 
3
- # rubocop:disable Documentation
4
3
  module ActiveInteraction
5
- class GroupedInput
4
+ class GroupedInput # rubocop:disable Style/Documentation
6
5
  # Required for Ruby <= 1.9.3.
7
6
  def [](name)
8
7
  send(name)
@@ -14,20 +13,66 @@ module ActiveInteraction
14
13
  end unless method_defined?(:[]=)
15
14
  end
16
15
 
17
- class Errors
16
+ class Errors # rubocop:disable Style/Documentation
18
17
  # Required for Rails < 3.2.13.
19
18
  protected :initialize_dup
19
+
20
+ # Required for Rails < 5.
21
+ #
22
+ # Extracted from active_model-errors_details 1.2.0. Modified to add support
23
+ # for ActiveModel 3.2.0.
24
+ module Details
25
+ extend ActiveSupport::Concern
26
+
27
+ CALLBACKS_OPTIONS = ::ActiveModel::Errors::CALLBACKS_OPTIONS
28
+ private_constant :CALLBACKS_OPTIONS
29
+
30
+ included do
31
+ attr_reader :details
32
+
33
+ %w[initialize initialize_dup add clear delete].each do |method|
34
+ alias_method_chain method, :details
35
+ end
36
+ end
37
+
38
+ def initialize_with_details(base)
39
+ @details = Hash.new { |details, attribute| details[attribute] = [] }
40
+ initialize_without_details(base)
41
+ end
42
+
43
+ def initialize_dup_with_details(other)
44
+ @details = other.details.deep_dup
45
+ initialize_dup_without_details(other)
46
+ end
47
+
48
+ def add_with_details(attribute, message = :invalid, options = {})
49
+ message = message.call if message.respond_to?(:call)
50
+ error = options.except(*CALLBACKS_OPTIONS).merge(error: message)
51
+ details[attribute].push(error)
52
+ add_without_details(attribute, message, options)
53
+ end
54
+
55
+ def clear_with_details
56
+ details.clear
57
+ clear_without_details
58
+ end
59
+
60
+ def delete_with_details(attribute)
61
+ details.delete(attribute)
62
+ delete_without_details(attribute)
63
+ end
64
+ end
65
+ include Details unless method_defined?(:details)
20
66
  end
21
- end
22
67
 
23
- # @private
24
- class Hash
25
- # Required for Rails < 4.0.0.
26
- def transform_keys
27
- result = {}
28
- each_key do |key|
29
- result[yield(key)] = self[key]
68
+ class HashFilter # rubocop:disable Style/Documentation
69
+ # Required for Rails < 4.0.0.
70
+ def self.transform_keys(hash, &block)
71
+ return hash.transform_keys(&block) if hash.respond_to?(:transform_keys)
72
+
73
+ result = {}
74
+ hash.each_key { |key| result[block.call(key)] = hash[key] }
75
+ result
30
76
  end
31
- result
32
- end unless method_defined?(:transform_keys)
77
+ end
33
78
  end
@@ -29,6 +29,7 @@ module ActiveInteraction
29
29
  # end
30
30
  class Base
31
31
  include ActiveModelable
32
+ include ActiveRecordable
32
33
  include Runnable
33
34
 
34
35
  define_callbacks :type_check
@@ -58,28 +59,6 @@ module ActiveInteraction
58
59
  #
59
60
  # @raise (see ActiveInteraction::Runnable::ClassMethods#run!)
60
61
 
61
- # @!method transaction(enable, options = {})
62
- # Configure transactions by enabling or disabling them and setting
63
- # their options.
64
- #
65
- # @example Disable transactions
66
- # Class.new(ActiveInteraction::Base) do
67
- # transaction false
68
- # end
69
- #
70
- # @example Use different transaction options
71
- # Class.new(ActiveInteraction::Base) do
72
- # transaction true, isolation: :serializable
73
- # end
74
- #
75
- # @param enable [Boolean] Should transactions be enabled?
76
- # @param options [Hash] Options to pass to
77
- # `ActiveRecord::Base.transaction`.
78
- #
79
- # @return [nil]
80
- #
81
- # @since 1.2.0
82
-
83
62
  # Get or set the description.
84
63
  #
85
64
  # @example
@@ -120,6 +99,11 @@ module ActiveInteraction
120
99
  end
121
100
  end
122
101
 
102
+ def model(*)
103
+ super
104
+ end
105
+ ActiveInteraction.deprecate self, :model, 'use `object` instead'
106
+
123
107
  private
124
108
 
125
109
  # @param klass [Class]
@@ -168,7 +152,13 @@ module ActiveInteraction
168
152
  attr_accessor filter.name
169
153
  define_method("#{filter.name}?") { !public_send(filter.name).nil? }
170
154
 
171
- filter.default if filter.default?
155
+ eagerly_evaluate_default(filter)
156
+ end
157
+
158
+ # @param filter [Filter]
159
+ def eagerly_evaluate_default(filter)
160
+ default = filter.options[:default]
161
+ filter.default if default && !default.is_a?(Proc)
172
162
  end
173
163
  end
174
164
 
@@ -181,31 +171,6 @@ module ActiveInteraction
181
171
  process_inputs(inputs.symbolize_keys)
182
172
  end
183
173
 
184
- # Returns the column object for the named filter.
185
- #
186
- # @param name [Symbol] The name of a filter.
187
- #
188
- # @example
189
- # class Interaction < ActiveInteraction::Base
190
- # string :email, default: nil
191
- #
192
- # def execute; end
193
- # end
194
- #
195
- # Interaction.new.column_for_attribute(:email)
196
- # # => #<ActiveInteraction::FilterColumn:0x007faebeb2a6c8 @type=:string>
197
- #
198
- # Interaction.new.column_for_attribute(:not_a_filter)
199
- # # => nil
200
- #
201
- # @return [FilterColumn, nil]
202
- #
203
- # @since 1.2.0
204
- def column_for_attribute(name)
205
- filter = self.class.filters[name]
206
- FilterColumn.intern(filter.database_column_type) if filter
207
- end
208
-
209
174
  # @!method compose(other, inputs = {})
210
175
  # Run another interaction and return its result. If the other interaction
211
176
  # fails, halt execution.
@@ -220,8 +185,7 @@ module ActiveInteraction
220
185
  #
221
186
  # Runs the business logic associated with the interaction. This method is
222
187
  # only run when there are no validation errors. The return value is
223
- # placed into {#result}. By default, this method is run in a transaction
224
- # if ActiveRecord is available (see {.transaction}).
188
+ # placed into {#result}.
225
189
  #
226
190
  # @raise (see ActiveInteraction::Runnable#execute)
227
191
 
@@ -264,7 +228,7 @@ module ActiveInteraction
264
228
  self.class.filters.each do |name, filter|
265
229
  begin
266
230
  public_send("#{name}=", filter.clean(inputs[name]))
267
- rescue InvalidValueError, MissingValueError, InvalidNestedValueError
231
+ rescue Error
268
232
  # #type_check will add errors if appropriate.
269
233
  end
270
234
  end
@@ -273,7 +237,7 @@ module ActiveInteraction
273
237
  def type_check
274
238
  run_callbacks(:type_check) do
275
239
  Validation.validate(self.class.filters, inputs).each do |error|
276
- errors.add_sym(*error)
240
+ errors.add(*error)
277
241
  end
278
242
  end
279
243
  end
@@ -0,0 +1,57 @@
1
+ # coding: utf-8
2
+
3
+ module ActiveInteraction
4
+ # Implement the minimal ActiveRecord interface.
5
+ #
6
+ # @private
7
+ module ActiveRecordable
8
+ # Returns the column object for the named filter.
9
+ #
10
+ # @param name [Symbol] The name of a filter.
11
+ #
12
+ # @example
13
+ # class Interaction < ActiveInteraction::Base
14
+ # string :email, default: nil
15
+ #
16
+ # def execute; end
17
+ # end
18
+ #
19
+ # Interaction.new.column_for_attribute(:email)
20
+ # # => #<ActiveInteraction::FilterColumn:0x007faebeb2a6c8 @type=:string>
21
+ #
22
+ # Interaction.new.column_for_attribute(:not_a_filter)
23
+ # # => nil
24
+ #
25
+ # @return [FilterColumn, nil]
26
+ #
27
+ # @since 1.2.0
28
+ def column_for_attribute(name)
29
+ filter = self.class.filters[name]
30
+ FilterColumn.intern(filter.database_column_type) if filter
31
+ end
32
+
33
+ # Returns true if a filter of that name exists.
34
+ #
35
+ # @param name [String, Symbol] The name of a filter.
36
+ #
37
+ # @example
38
+ # class Interaction < ActiveInteraction::Base
39
+ # string :email, default: nil
40
+ #
41
+ # def execute; end
42
+ # end
43
+ #
44
+ # Interaction.new.has_attribute?(:email)
45
+ # # => true
46
+ #
47
+ # Interaction.new.has_attribute?(:not_a_filter)
48
+ # # => false
49
+ #
50
+ # @return [Boolean]
51
+ #
52
+ # @since 1.5.0
53
+ def has_attribute?(name) # rubocop:disable Style/PredicateName
54
+ self.class.filters.key?(name.to_sym)
55
+ end
56
+ end
57
+ end
@@ -6,14 +6,12 @@ module ActiveInteraction
6
6
  #
7
7
  # @note Must be included after `ActiveModel::Validations`.
8
8
  #
9
- # Runs code in transactions and only provides the result if there are no
10
- # validation errors.
9
+ # Runs code and provides the result.
11
10
  #
12
11
  # @private
13
12
  module Runnable
14
13
  extend ActiveSupport::Concern
15
14
  include ActiveModel::Validations
16
- include ActiveInteraction::Transactable
17
15
 
18
16
  included do
19
17
  define_callbacks :execute
@@ -41,13 +39,8 @@ module ActiveInteraction
41
39
  #
42
40
  # @return (see #result)
43
41
  def result=(result)
44
- if errors.empty?
45
- @_interaction_result = result
46
- @_interaction_valid = true
47
- else
48
- @_interaction_result = nil
49
- @_interaction_valid = false
50
- end
42
+ @_interaction_result = result
43
+ @_interaction_valid = errors.empty?
51
44
  end
52
45
 
53
46
  # @return [Boolean]
@@ -82,15 +75,12 @@ module ActiveInteraction
82
75
  def run
83
76
  return unless valid?
84
77
 
85
- self.result = transaction do
78
+ self.result =
86
79
  begin
87
80
  run_callbacks(:execute) { execute }
88
81
  rescue Interrupt => interrupt
89
82
  merge_errors_onto_base(interrupt.outcome.errors)
90
-
91
- raise ActiveRecord::Rollback if self.class.transaction?
92
83
  end
93
- end
94
84
  end
95
85
 
96
86
  def merge_errors_onto_base(new_errors)
@@ -67,7 +67,7 @@ module ActiveInteraction
67
67
  # @param filter_name [Symbol]
68
68
  # @param input_value [Object]
69
69
  def initialize(filter_name, input_value)
70
- super()
70
+ super("#{filter_name}: #{input_value.inspect}")
71
71
 
72
72
  @filter_name = filter_name
73
73
  @input_value = input_value
@@ -89,72 +89,20 @@ module ActiveInteraction
89
89
  end
90
90
  private_constant :Interrupt
91
91
 
92
- # An extension that provides symbolic error messages to make introspection
93
- # and testing easier.
92
+ # An extension that provides the ability to merge other errors into itself.
94
93
  class Errors < ActiveModel::Errors
95
- # Maps attributes to arrays of symbolic messages.
96
- #
97
- # @return [Hash{Symbol => Array<Symbol>}]
98
- attr_reader :symbolic
99
-
100
- # Adds a symbolic error message to an attribute.
101
- #
102
- # @example
103
- # errors.add_sym(:attribute)
104
- # errors.symbolic
105
- # # => {:attribute=>[:invalid]}
106
- # errors.messages
107
- # # => {:attribute=>["is invalid"]}
108
- #
109
- # @param attribute [Symbol] The attribute to add an error to.
110
- # @param symbol [Symbol, nil] The symbolic error to add.
111
- # @param message [String, Symbol, Proc, nil] The message to add.
112
- # @param options [Hash]
113
- #
114
- # @return (see #symbolic)
115
- #
116
- # @see ActiveModel::Errors#add
117
- def add_sym(attribute, symbol = :invalid, message = nil, options = {})
118
- add(attribute, message || symbol, options)
119
-
120
- symbolic[attribute] += [symbol]
121
- end
122
-
123
- # @see ActiveModel::Errors#initialize
124
- #
125
- # @private
126
- def initialize(*)
127
- @symbolic = Hash.new([]).with_indifferent_access
128
-
129
- super
130
- end
131
-
132
- # @see ActiveModel::Errors#initialize_dup
133
- #
134
- # @private
135
- def initialize_dup(other)
136
- @symbolic = other.symbolic.with_indifferent_access
137
-
138
- super
139
- end
140
-
141
- # @see ActiveModel::Errors#clear
142
- #
143
- # @private
144
- def clear
145
- symbolic.clear
146
-
147
- super
148
- end
149
-
150
94
  # Merge other errors into this one.
151
95
  #
152
96
  # @param other [Errors]
153
97
  #
154
98
  # @return [Errors]
155
99
  def merge!(other)
156
- merge_messages!(other)
157
- merge_symbolic!(other) if other.respond_to?(:symbolic)
100
+ if other.respond_to?(:details)
101
+ merge_details!(other)
102
+ else
103
+ merge_messages!(other)
104
+ end
105
+
158
106
  self
159
107
  end
160
108
 
@@ -168,12 +116,12 @@ module ActiveInteraction
168
116
  end
169
117
  end
170
118
 
171
- def merge_symbolic!(other)
172
- other.symbolic.each do |attribute, symbols|
173
- symbols.each do |symbol|
174
- next if symbolic[attribute].include?(symbol)
175
-
176
- symbolic[attribute] += [symbol]
119
+ def merge_details!(other)
120
+ other.details.each do |attribute, details|
121
+ details.each do |detail|
122
+ detail = detail.dup
123
+ error = detail.delete(:error)
124
+ add(attribute, error, detail) unless added?(attribute, error, detail)
177
125
  end
178
126
  end
179
127
  end
@@ -48,30 +48,33 @@ module ActiveInteraction
48
48
  super do |klass, names, options|
49
49
  filter = klass.new(name.to_s.singularize.to_sym, options, &block)
50
50
 
51
- validate(filter, names)
51
+ validate!(filter, names)
52
52
 
53
53
  filters[filter.name] = filter
54
54
  end
55
55
  end
56
56
 
57
+ def model(*)
58
+ super
59
+ end
60
+ ActiveInteraction.deprecate self, :model, 'use `object` instead'
61
+
57
62
  private
58
63
 
59
64
  # @return [Array<Class>]
60
65
  def classes
61
66
  result = [Array]
67
+ return result unless Object.const_defined?(:ActiveRecord)
68
+ return result unless ActiveRecord.const_defined?(:Relation)
62
69
 
63
- if ActiveRecord.const_defined?(:Relation)
64
- result.push(ActiveRecord::Relation)
65
- end
66
-
67
- result
70
+ result.push(ActiveRecord::Relation)
68
71
  end
69
72
 
70
73
  # @param filter [Filter]
71
74
  # @param names [Array<Symbol>]
72
75
  #
73
76
  # @raise [InvalidFilterError]
74
- def validate(filter, names)
77
+ def validate!(filter, names)
75
78
  unless filters.empty?
76
79
  fail InvalidFilterError, 'multiple filters in array block'
77
80
  end
@@ -80,11 +83,11 @@ module ActiveInteraction
80
83
  fail InvalidFilterError, 'attribute names in array block'
81
84
  end
82
85
 
83
- # rubocop:disable GuardClause
84
86
  if filter.default?
85
87
  fail InvalidDefaultError, 'default values in array block'
86
88
  end
87
- # rubocop:enable GuardClause
89
+
90
+ nil
88
91
  end
89
92
  end
90
93
  end
@@ -4,9 +4,8 @@ module ActiveInteraction
4
4
  class Base
5
5
  # @!method self.file(*attributes, options = {})
6
6
  # Creates accessors for the attributes and ensures that values passed to
7
- # the attributes are Files or Tempfiles. It will also extract a file
8
- # from any object with a `tempfile` method. This is useful when passing
9
- # in Rails params that include a file upload.
7
+ # the attributes respond to the `eof?` method. This is useful when passing
8
+ # in Rails params that include a file upload or another generic IO object.
10
9
  #
11
10
  # @!macro filter_method_params
12
11
  #
@@ -15,35 +14,17 @@ module ActiveInteraction
15
14
  end
16
15
 
17
16
  # @private
18
- class FileFilter < Filter
17
+ class FileFilter < InterfaceFilter
19
18
  register :file
20
19
 
21
- def cast(value)
22
- value = extract_file(value)
23
-
24
- case value
25
- when File, Tempfile
26
- value
27
- else
28
- super
29
- end
30
- end
31
-
32
20
  def database_column_type
33
21
  self.class.slug
34
22
  end
35
23
 
36
24
  private
37
25
 
38
- # @param value [File, #tempfile]
39
- #
40
- # @return [File]
41
- def extract_file(value)
42
- if value.respond_to?(:tempfile)
43
- value.tempfile
44
- else
45
- value
46
- end
26
+ def methods
27
+ [:eof?]
47
28
  end
48
29
  end
49
30
  end
@@ -8,17 +8,13 @@ module ActiveInteraction
8
8
  #
9
9
  # @!macro filter_method_params
10
10
  # @param block [Proc] filter methods to apply for select keys
11
- # @option options [Boolean] :strip (true) strip unknown keys (Note: All
12
- # keys are symbolized. Ruby does not GC symbols so this can cause
13
- # memory bloat. Setting this option to `false` and passing in non-safe
14
- # input (e.g. Rails `params`) opens your software to a denial of
15
- # service attack.)
11
+ # @option options [Boolean] :strip (true) remove unknown keys
16
12
  #
17
13
  # @example
18
14
  # hash :order
19
15
  # @example
20
16
  # hash :order do
21
- # model :item
17
+ # object :item
22
18
  # integer :quantity, default: 1
23
19
  # end
24
20
  end
@@ -32,11 +28,12 @@ module ActiveInteraction
32
28
  def cast(value)
33
29
  case value
34
30
  when Hash
35
- value = stringify_the_symbol_keys(value)
31
+ value = value.with_indifferent_access
32
+ initial = strip? ? ActiveSupport::HashWithIndifferentAccess.new : value
36
33
 
37
- filters.each_with_object(strip? ? {} : value) do |(name, filter), h|
34
+ filters.each_with_object(initial) do |(name, filter), h|
38
35
  clean_value(h, name.to_s, filter, value)
39
- end.symbolize_keys
36
+ end
40
37
  else
41
38
  super
42
39
  end
@@ -52,6 +49,11 @@ module ActiveInteraction
52
49
  end
53
50
  end
54
51
 
52
+ def model(*)
53
+ super
54
+ end
55
+ ActiveInteraction.deprecate self, :model, 'use `object` instead'
56
+
55
57
  private
56
58
 
57
59
  def clean_value(h, name, filter, value)
@@ -74,9 +76,5 @@ module ActiveInteraction
74
76
  def strip?
75
77
  options.fetch(:strip, true)
76
78
  end
77
-
78
- def stringify_the_symbol_keys(hash)
79
- hash.transform_keys { |key| key.is_a?(Symbol) ? key.to_s : key }
80
- end
81
79
  end
82
80
  end
@@ -7,8 +7,8 @@ module ActiveInteraction
7
7
  # the attributes implement an interface.
8
8
  #
9
9
  # @!macro filter_method_params
10
- # @option options [Array<Symbol>] :methods ([]) the methods that objects
11
- # conforming to this interface should respond to
10
+ # @option options [Array<String,Symbol>] :methods ([]) the methods that
11
+ # objects conforming to this interface should respond to
12
12
  #
13
13
  # @example
14
14
  # interface :anything
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # @!method self.model(*attributes, options = {})
5
+ # @!method self.object(*attributes, options = {})
6
6
  # Creates accessors for the attributes and ensures that values passed to
7
7
  # the attributes are the correct class.
8
8
  #
@@ -11,14 +11,14 @@ module ActiveInteraction
11
11
  # Class name used to ensure the value.
12
12
  #
13
13
  # @example
14
- # model :account
14
+ # object :account
15
15
  # @example
16
- # model :account, class: User
16
+ # object :account, class: User
17
17
  end
18
18
 
19
19
  # @private
20
- class ModelFilter < Filter
21
- register :model
20
+ class ObjectFilter < Filter
21
+ register :object
22
22
 
23
23
  def cast(value, reconstantize = true)
24
24
  @klass ||= klass
@@ -49,7 +49,7 @@ module ActiveInteraction
49
49
  #
50
50
  # @return [Boolean]
51
51
  def matches?(value)
52
- @klass === value || # rubocop:disable CaseEquality
52
+ @klass === value || # rubocop:disable Style/CaseEquality
53
53
  value.is_a?(@klass)
54
54
  end
55
55
  end
@@ -17,7 +17,7 @@ en:
17
17
  hash: hash
18
18
  integer: integer
19
19
  interface: interface
20
- model: model
20
+ object: object
21
21
  string: string
22
22
  symbol: symbol
23
23
  time: time
@@ -25,10 +25,10 @@ module ActiveInteraction
25
25
  def error_args(filter, error)
26
26
  case error
27
27
  when InvalidNestedValueError
28
- [filter.name, :invalid_nested, nil,
28
+ [filter.name, :invalid_nested,
29
29
  name: error.filter_name.inspect, value: error.input_value.inspect]
30
30
  when InvalidValueError
31
- [filter.name, :invalid_type, nil, type: type(filter)]
31
+ [filter.name, :invalid_type, type: type(filter)]
32
32
  when MissingValueError
33
33
  [filter.name, :missing]
34
34
  end
@@ -5,5 +5,5 @@ module ActiveInteraction
5
5
  # The version number.
6
6
  #
7
7
  # @return [Gem::Version]
8
- VERSION = Gem::Version.new('1.4.1')
8
+ VERSION = Gem::Version.new('2.0.0')
9
9
  end