active_interaction 0.10.2 → 1.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 +4 -4
- data/CHANGELOG.md +14 -1
- data/README.md +32 -35
- data/lib/active_interaction.rb +14 -6
- data/lib/active_interaction/base.rb +155 -135
- data/lib/active_interaction/concerns/active_modelable.rb +46 -0
- data/lib/active_interaction/{modules/overload_hash.rb → concerns/hashable.rb} +5 -1
- data/lib/active_interaction/concerns/missable.rb +47 -0
- data/lib/active_interaction/concerns/runnable.rb +156 -0
- data/lib/active_interaction/errors.rb +50 -14
- data/lib/active_interaction/filter.rb +33 -45
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +24 -15
- data/lib/active_interaction/filters/abstract_filter.rb +18 -0
- data/lib/active_interaction/filters/abstract_numeric_filter.rb +13 -8
- data/lib/active_interaction/filters/array_filter.rb +42 -35
- data/lib/active_interaction/filters/boolean_filter.rb +8 -11
- data/lib/active_interaction/filters/date_filter.rb +11 -15
- data/lib/active_interaction/filters/date_time_filter.rb +11 -15
- data/lib/active_interaction/filters/file_filter.rb +11 -11
- data/lib/active_interaction/filters/float_filter.rb +7 -15
- data/lib/active_interaction/filters/hash_filter.rb +18 -24
- data/lib/active_interaction/filters/integer_filter.rb +7 -14
- data/lib/active_interaction/filters/model_filter.rb +13 -14
- data/lib/active_interaction/filters/string_filter.rb +11 -14
- data/lib/active_interaction/filters/symbol_filter.rb +6 -9
- data/lib/active_interaction/filters/time_filter.rb +13 -16
- data/lib/active_interaction/modules/validation.rb +9 -4
- data/lib/active_interaction/version.rb +5 -2
- data/spec/active_interaction/base_spec.rb +109 -4
- data/spec/active_interaction/{modules/active_model_spec.rb → concerns/active_modelable_spec.rb} +15 -3
- data/spec/active_interaction/{modules/overload_hash_spec.rb → concerns/hashable_spec.rb} +2 -3
- data/spec/active_interaction/{modules/method_missing_spec.rb → concerns/missable_spec.rb} +38 -3
- data/spec/active_interaction/concerns/runnable_spec.rb +192 -0
- data/spec/active_interaction/filters/abstract_filter_spec.rb +8 -0
- data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -1
- data/spec/active_interaction/modules/validation_spec.rb +2 -2
- data/spec/support/concerns.rb +15 -0
- data/spec/support/filters.rb +5 -5
- data/spec/support/interactions.rb +6 -5
- metadata +47 -73
- data/lib/active_interaction/filters.rb +0 -28
- data/lib/active_interaction/modules/active_model.rb +0 -32
- data/lib/active_interaction/modules/core.rb +0 -70
- data/lib/active_interaction/modules/method_missing.rb +0 -20
- data/spec/active_interaction/filters_spec.rb +0 -23
- data/spec/active_interaction/modules/core_spec.rb +0 -114
@@ -2,25 +2,18 @@
|
|
2
2
|
|
3
3
|
module ActiveInteraction
|
4
4
|
class Base
|
5
|
-
#
|
6
|
-
# the attributes
|
5
|
+
# @!method self.integer(*attributes, options = {})
|
6
|
+
# Creates accessors for the attributes and ensures that values passed to
|
7
|
+
# the attributes are Integers. String values are converted into
|
8
|
+
# Integers.
|
7
9
|
#
|
8
|
-
#
|
10
|
+
# @!macro filter_method_params
|
9
11
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# @since 0.1.0
|
14
|
-
#
|
15
|
-
# @method self.integer(*attributes, options = {})
|
12
|
+
# @example
|
13
|
+
# integer :quantity
|
16
14
|
end
|
17
15
|
|
18
16
|
# @private
|
19
17
|
class IntegerFilter < AbstractNumericFilter
|
20
|
-
private
|
21
|
-
|
22
|
-
def klass
|
23
|
-
Integer
|
24
|
-
end
|
25
18
|
end
|
26
19
|
end
|
@@ -2,22 +2,18 @@
|
|
2
2
|
|
3
3
|
module ActiveInteraction
|
4
4
|
class Base
|
5
|
-
#
|
6
|
-
# the attributes
|
5
|
+
# @!method self.model(*attributes, options = {})
|
6
|
+
# Creates accessors for the attributes and ensures that values passed to
|
7
|
+
# the attributes are the correct class.
|
7
8
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# @!macro filter_method_params
|
10
|
+
# @option options [Class, String, Symbol] :class (use the attribute name)
|
11
|
+
# Class name used to ensure the value.
|
11
12
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# model :account, class: User
|
17
|
-
#
|
18
|
-
# @since 0.1.0
|
19
|
-
#
|
20
|
-
# @method self.model(*attributes, options = {})
|
13
|
+
# @example
|
14
|
+
# model :account
|
15
|
+
# @example
|
16
|
+
# model :account, class: User
|
21
17
|
end
|
22
18
|
|
23
19
|
# @private
|
@@ -33,6 +29,9 @@ module ActiveInteraction
|
|
33
29
|
|
34
30
|
private
|
35
31
|
|
32
|
+
# @return [Class]
|
33
|
+
#
|
34
|
+
# @raise [InvalidClassError]
|
36
35
|
def klass
|
37
36
|
klass_name = options.fetch(:class, name).to_s.classify
|
38
37
|
klass_name.constantize
|
@@ -2,22 +2,18 @@
|
|
2
2
|
|
3
3
|
module ActiveInteraction
|
4
4
|
class Base
|
5
|
-
#
|
6
|
-
# the attributes
|
5
|
+
# @!method self.string(*attributes, options = {})
|
6
|
+
# Creates accessors for the attributes and ensures that values passed to
|
7
|
+
# the attributes are Strings.
|
7
8
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# @!macro filter_method_params
|
10
|
+
# @option options [Boolean] :strip (true) strip leading and trailing
|
11
|
+
# whitespace
|
11
12
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# string :first_name, strip: false
|
17
|
-
#
|
18
|
-
# @since 0.1.0
|
19
|
-
#
|
20
|
-
# @method self.string(*attributes, options = {})
|
13
|
+
# @example
|
14
|
+
# string :first_name
|
15
|
+
# @example
|
16
|
+
# string :first_name, strip: false
|
21
17
|
end
|
22
18
|
|
23
19
|
# @private
|
@@ -33,6 +29,7 @@ module ActiveInteraction
|
|
33
29
|
|
34
30
|
private
|
35
31
|
|
32
|
+
# @return [Boolean]
|
36
33
|
def strip?
|
37
34
|
options.fetch(:strip, true)
|
38
35
|
end
|
@@ -2,17 +2,14 @@
|
|
2
2
|
|
3
3
|
module ActiveInteraction
|
4
4
|
class Base
|
5
|
-
#
|
6
|
-
# the attributes
|
5
|
+
# @!method self.symbol(*attributes, options = {})
|
6
|
+
# Creates accessors for the attributes and ensures that values passed to
|
7
|
+
# the attributes are Symbols. Strings will be converted to Symbols.
|
7
8
|
#
|
8
|
-
#
|
9
|
+
# @!macro filter_method_params
|
9
10
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# @since 0.6.0
|
14
|
-
#
|
15
|
-
# @method self.symbol(*attributes, options = {})
|
11
|
+
# @example
|
12
|
+
# symbol :condiment
|
16
13
|
end
|
17
14
|
|
18
15
|
# @private
|
@@ -2,24 +2,21 @@
|
|
2
2
|
|
3
3
|
module ActiveInteraction
|
4
4
|
class Base
|
5
|
-
#
|
6
|
-
# the attributes
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
5
|
+
# @!method self.time(*attributes, options = {})
|
6
|
+
# Creates accessors for the attributes and ensures that values passed to
|
7
|
+
# the attributes are Times. Numeric values are processed using `at`.
|
8
|
+
# Strings are processed using `parse` unless the format option is
|
9
|
+
# given, in which case they will be processed with `strptime`. If
|
10
|
+
# `Time.zone` is available it will be used so that the values are time
|
11
|
+
# zone aware.
|
10
12
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
+
# @!macro filter_method_params
|
14
|
+
# @option options [String] :format parse strings using this format string
|
13
15
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# time :start_date, format: '%Y-%m-%dT%H:%M:%S%z'
|
19
|
-
#
|
20
|
-
# @since 0.1.0
|
21
|
-
#
|
22
|
-
# @method self.time(*attributes, options = {})
|
16
|
+
# @example
|
17
|
+
# time :start_date
|
18
|
+
# @example
|
19
|
+
# time :start_date, format: '%Y-%m-%dT%H:%M:%S%Z'
|
23
20
|
end
|
24
21
|
|
25
22
|
# @private
|
@@ -1,22 +1,27 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
module ActiveInteraction
|
4
|
+
# Validates inputs using filters.
|
5
|
+
#
|
4
6
|
# @private
|
5
7
|
module Validation
|
8
|
+
# @param filters [Hash{Symbol => Filter}]
|
9
|
+
# @param inputs [Hash{Symbol => Object}]
|
6
10
|
def self.validate(filters, inputs)
|
7
|
-
filters.each_with_object([]) do |filter, errors|
|
11
|
+
filters.each_with_object([]) do |(name, filter), errors|
|
8
12
|
begin
|
9
|
-
filter.cast(inputs[
|
13
|
+
filter.cast(inputs[name])
|
10
14
|
rescue InvalidValueError
|
11
|
-
errors << [
|
15
|
+
errors << [name, :invalid, nil, type: type(filter)]
|
12
16
|
rescue MissingValueError
|
13
|
-
errors << [
|
17
|
+
errors << [name, :missing]
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
18
22
|
private
|
19
23
|
|
24
|
+
# @param filter [Filter]
|
20
25
|
def self.type(filter)
|
21
26
|
I18n.translate("#{Base.i18n_scope}.types.#{filter.class.slug}")
|
22
27
|
end
|
@@ -118,6 +118,23 @@ describe ActiveInteraction::Base do
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
+
describe '.desc' do
|
122
|
+
let(:desc) { SecureRandom.hex }
|
123
|
+
|
124
|
+
it 'returns nil' do
|
125
|
+
expect(described_class.desc).to be_nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'returns the description' do
|
129
|
+
expect(described_class.desc(desc)).to eq desc
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'saves the description' do
|
133
|
+
described_class.desc(desc)
|
134
|
+
expect(described_class.desc).to eq desc
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
121
138
|
describe '.method_missing(filter_type, *args, &block)' do
|
122
139
|
it 'raises an error for an invalid filter type' do
|
123
140
|
expect do
|
@@ -246,10 +263,10 @@ describe ActiveInteraction::Base do
|
|
246
263
|
expect(result[:thing]).to eq thing
|
247
264
|
end
|
248
265
|
|
249
|
-
it 'calls transaction' do
|
250
|
-
allow(
|
266
|
+
it 'calls ActiveRecord::Base.transaction' do
|
267
|
+
allow(ActiveRecord::Base).to receive(:transaction)
|
251
268
|
outcome
|
252
|
-
expect(
|
269
|
+
expect(ActiveRecord::Base).to have_received(:transaction)
|
253
270
|
.with(no_args)
|
254
271
|
end
|
255
272
|
end
|
@@ -331,7 +348,7 @@ describe ActiveInteraction::Base do
|
|
331
348
|
let(:described_class) { InteractionWithFilter }
|
332
349
|
|
333
350
|
def filters(klass)
|
334
|
-
klass.filters.
|
351
|
+
klass.filters.keys
|
335
352
|
end
|
336
353
|
|
337
354
|
it 'includes the filters from the superclass' do
|
@@ -344,4 +361,92 @@ describe ActiveInteraction::Base do
|
|
344
361
|
expect(filters(described_class)).to_not include :other_thing
|
345
362
|
end
|
346
363
|
end
|
364
|
+
|
365
|
+
context 'predicates' do
|
366
|
+
let(:described_class) { InteractionWithFilter }
|
367
|
+
|
368
|
+
it 'responds to the predicate' do
|
369
|
+
expect(interaction.respond_to?(:thing?)).to be_true
|
370
|
+
end
|
371
|
+
|
372
|
+
context 'without a value' do
|
373
|
+
it 'returns false' do
|
374
|
+
expect(interaction.thing?).to be_false
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
context 'with a value' do
|
379
|
+
let(:thing) { rand }
|
380
|
+
|
381
|
+
before do
|
382
|
+
inputs.merge!(thing: thing)
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'returns true' do
|
386
|
+
expect(interaction.thing?).to be_true
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
describe '.import_filters' do
|
392
|
+
let(:described_class) do
|
393
|
+
Class.new(TestInteraction) do
|
394
|
+
import_filters AddInteraction
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'imports the filters' do
|
399
|
+
expect(described_class.filters).to eq AddInteraction.filters
|
400
|
+
end
|
401
|
+
|
402
|
+
context 'with :only' do
|
403
|
+
let(:described_class) do
|
404
|
+
Class.new(TestInteraction) do
|
405
|
+
import_filters AddInteraction, only: [:x]
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
it 'does not modify the source' do
|
410
|
+
filters = AddInteraction.filters.dup
|
411
|
+
described_class
|
412
|
+
expect(AddInteraction.filters).to eq filters
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'imports the filters' do
|
416
|
+
expect(described_class.filters).to eq AddInteraction.filters
|
417
|
+
.select { |k, _| k == :x }
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
context 'with :except' do
|
422
|
+
let(:described_class) do
|
423
|
+
Class.new(TestInteraction) do
|
424
|
+
import_filters AddInteraction, except: [:x]
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
it 'does not modify the source' do
|
429
|
+
filters = AddInteraction.filters.dup
|
430
|
+
described_class
|
431
|
+
expect(AddInteraction.filters).to eq filters
|
432
|
+
end
|
433
|
+
|
434
|
+
it 'imports the filters' do
|
435
|
+
expect(described_class.filters).to eq AddInteraction.filters
|
436
|
+
.reject { |k, _| k == :x }
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
context 'with :only & :except' do
|
441
|
+
let(:described_class) do
|
442
|
+
Class.new(TestInteraction) do
|
443
|
+
import_filters AddInteraction, only: nil, except: nil
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
it 'raises an error' do
|
448
|
+
expect { described_class }.to raise_error ArgumentError
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
347
452
|
end
|
data/spec/active_interaction/{modules/active_model_spec.rb → concerns/active_modelable_spec.rb}
RENAMED
@@ -1,10 +1,22 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
|
+
require 'test/unit/assertions'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
shared_examples_for 'ActiveModel' do
|
7
|
+
include ActiveModel::Lint::Tests
|
8
|
+
include Test::Unit::Assertions
|
9
|
+
|
10
|
+
let(:model) { subject }
|
11
|
+
|
12
|
+
ActiveModel::Lint::Tests.public_instance_methods
|
13
|
+
.grep(/\Atest/) { |m| example(m) { public_send(m) } }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ActiveInteraction::ActiveModelable do
|
17
|
+
include_context 'concerns', ActiveInteraction::ActiveModelable
|
18
|
+
|
19
|
+
it_behaves_like 'ActiveModel'
|
8
20
|
|
9
21
|
describe '.i18n_scope' do
|
10
22
|
it 'returns the scope' do
|
@@ -2,9 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe ActiveInteraction::
|
6
|
-
|
7
|
-
subject(:instance) { klass.new }
|
5
|
+
describe ActiveInteraction::Hashable do
|
6
|
+
include_context 'concerns', ActiveInteraction::Hashable
|
8
7
|
|
9
8
|
describe '#hash(*args, &block)' do
|
10
9
|
context 'with no arguments' do
|
@@ -2,9 +2,44 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe ActiveInteraction::
|
6
|
-
|
7
|
-
|
5
|
+
describe ActiveInteraction::Missable do
|
6
|
+
include_context 'concerns', ActiveInteraction::Missable
|
7
|
+
|
8
|
+
describe '#respond_to?(slug, include_all = false)' do
|
9
|
+
context 'with invalid slug' do
|
10
|
+
let(:slug) { :slug }
|
11
|
+
|
12
|
+
it 'returns false' do
|
13
|
+
expect(instance.respond_to?(slug)).to be_false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with valid slug' do
|
18
|
+
let(:slug) { :boolean }
|
19
|
+
|
20
|
+
it 'returns true' do
|
21
|
+
expect(instance.respond_to?(slug)).to be_true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#method(sym)' do
|
27
|
+
context 'with invalid slug' do
|
28
|
+
let(:slug) { :slug }
|
29
|
+
|
30
|
+
it 'returns false' do
|
31
|
+
expect { instance.method(slug) }.to raise_error NameError
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with valid slug' do
|
36
|
+
let(:slug) { :boolean }
|
37
|
+
|
38
|
+
it 'returns true' do
|
39
|
+
expect(instance.method(slug)).to be_a Method
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
8
43
|
|
9
44
|
describe '#method_missing' do
|
10
45
|
context 'with invalid slug' do
|