active_entity 0.1.0 → 0.2.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
  SHA1:
3
- metadata.gz: 1072bc0bff5756103e46f5e88ca83b624b43c7a2
4
- data.tar.gz: 14e335691227ad6abd7045a4f33f6c0f89859966
3
+ metadata.gz: 7f64f30d78308e4f5887107685f1c7801485138f
4
+ data.tar.gz: 477079a529608f27cc99ed5be0e6da67edfddd65
5
5
  SHA512:
6
- metadata.gz: c2243185908d2889faa3ee05d8feb5dbe5b95e96a507a8bba191ce40784ad3637353d2587c2147757fab0c7f094644f5d9560f4179d1d6fd255008a7de4f7a20
7
- data.tar.gz: aaea48011b23cb87fbe1a3140051aac64e6366799f64619dfaa34bb861cf2dc7b5439040a5e9ca1420278e670871380436ebac9ef7248eca3bbbe18cd8c1fed1
6
+ metadata.gz: cd7a1b494c16cd2c2fcc0717f1eebe40310abfed70e340e2ecccd650ee3fadcd1ec8fc1d65d04c7f45feecce50f0d7d3c1ac94a6fcbd1ba11f3e8cc07b938f16
7
+ data.tar.gz: 8f6cadc7b710a9de8a3c4d839fdd123b754ef85c77dccb32765eb58b4cf24899f8177c4d5571fd8911721d3b1eaa2e4e765cc7642c085ed1d32b7cf3d461b581
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.2.0
2
+ * Introduce explicit type conversion and implicit coercion extension.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ActiveEntity
2
2
 
3
- To make an entity with ease according to ActiveModel way.
3
+ An extension for Active Model to encourage implementing entity.
4
4
 
5
5
  [![Build Status](https://travis-ci.org/taiki45/active_entity.svg?branch=master)](https://travis-ci.org/taiki45/active_entity) [![Coverage Status](https://coveralls.io/repos/taiki45/active_entity/badge.svg)](https://coveralls.io/r/taiki45/active_entity) [![Code Climate](https://codeclimate.com/github/taiki45/active_entity/badges/gpa.svg)](https://codeclimate.com/github/taiki45/active_entity) [![Gem Version](https://badge.fury.io/rb/active_entity.svg)](http://badge.fury.io/rb/active_entity)
6
6
 
@@ -23,9 +23,10 @@ Or install it yourself as:
23
23
  ## Synopsis
24
24
 
25
25
  ```ruby
26
+ # = Define model attributes with accessor and define identities =
26
27
  class Message
27
28
  include ActiveModel::Model
28
- include ActiveEntity::Attribute
29
+ include ActiveEntity::Accessor
29
30
  include ActiveEntity::Identity
30
31
 
31
32
  attribute :title
@@ -47,6 +48,40 @@ expect(message.attributes).to eq({ "title" => "A README of ActiveEntity", "body"
47
48
 
48
49
  another_messsage = Message.new(title: 'A README of ActiveEntity', body: '')
49
50
  expect(message).to eq(another_messsage)
51
+
52
+
53
+ # = Coercion =
54
+ class Person
55
+ include ActiveModel::Model
56
+ include ActiveEntity::Accessor
57
+ include ActiveEntity::Coercion
58
+
59
+ attribute :name, type: String
60
+ attribute :age, type: Integer
61
+ end
62
+
63
+ alice = Person.new(name: 'Alice', age: '1')
64
+ expect(alice.name).to eq('Alice')
65
+ expect(alice.age).to eq(1)
66
+
67
+
68
+ # = Typecasting =
69
+ class Recipe
70
+ include ActiveModel::Model
71
+ include ActiveEntity::Accessor
72
+ include ActiveEntity::Typecasting
73
+
74
+ attribute :title, type: String
75
+ attribute :steps, type: Integer
76
+ attribute :likes, type: Integer
77
+ end
78
+
79
+ waffle = Recipe.new(title: 'Waffle', steps: '12', likes: 'abc')
80
+ expect { waffle.cast! }.to raise_error(ActiveEntity::CastError)
81
+
82
+ expect(waffle.title).to eq('Waffle')
83
+ expect(waffle.steps).to eq('12') #=> rollbacks casted value on error
84
+ expect(waffle.likes).to eq('abc')
50
85
  ```
51
86
 
52
87
  ## Contributing
data/lib/active_entity.rb CHANGED
@@ -1,6 +1,16 @@
1
1
  require 'active_model'
2
2
  require 'active_support/concern'
3
+ require 'active_support/core_ext/string'
3
4
 
4
5
  require 'active_entity/version'
6
+ require 'active_entity/errors'
5
7
  require 'active_entity/attribute'
8
+ require 'active_entity/accessor'
9
+ require 'active_entity/typecasting'
10
+ require 'active_entity/coercion'
6
11
  require 'active_entity/identity'
12
+
13
+ # TODO: to_param
14
+
15
+ require 'active_entity/conversion_definitions'
16
+ require 'active_entity/default_definitions'
@@ -0,0 +1,21 @@
1
+ module ActiveEntity
2
+ module Accessor
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :defined_attributes, instance_writer: false, instance_predicate: false
7
+ self.defined_attributes = {}
8
+ end
9
+
10
+ class_methods do
11
+ def attribute(name, options = {})
12
+ defined_attributes[name] = Attribute.new(name, options)
13
+ attr_accessor(name)
14
+ end
15
+ end
16
+
17
+ def attributes
18
+ Hash[defined_attributes.keys.map {|name| [name.to_s, public_send(name)] }]
19
+ end
20
+ end
21
+ end
@@ -1,21 +1,69 @@
1
1
  module ActiveEntity
2
- module Attribute
3
- extend ActiveSupport::Concern
2
+ # @example Cast with given type option.
3
+ # attr = ActiveEntity::Attribute.new(:user_id, type: Integer)
4
+ # attr.cast!('12') #=> 12
5
+ # attr.cast!('a') #=> raises ActiveEntity::CastError
6
+ #
7
+ # @example Cast with given casting procedure.
8
+ # attr = ActiveEntity::Attribute.new(
9
+ # :created_at,
10
+ # cast_by: -> (value) do
11
+ # case value
12
+ # when Fixnum, Float
13
+ # Time.at(value)
14
+ # when String
15
+ # Time.parse(value)
16
+ # else
17
+ # raise ActiveEntity::CastError.build(:Time, value)
18
+ # end
19
+ # end
20
+ # )
21
+ # attr.cast!(1420081200) #=> a Time object
22
+ # attr.cast!('2015/01/01 12:00:00') #=> a Time object
23
+ class Attribute
24
+ attr_reader :name, :options
4
25
 
5
- included do
6
- class_attribute :defined_attributes, instance_writer: false, instance_predicate: false
7
- self.defined_attributes = {}
26
+ # @param [Symbol] name
27
+ # @param [Hash{Symbol => Object}] options
28
+ def initialize(name, options = {})
29
+ raise ArgumentError unless options.is_a?(Hash)
30
+ @name, @options = name, options
8
31
  end
9
32
 
10
- class_methods do
11
- def attribute(name, options = {})
12
- defined_attributes[name] = options
13
- attr_accessor(name)
33
+ # @return [Class, Symbol, String]
34
+ def type
35
+ @options[:type]
36
+ end
37
+
38
+ # @return [Proc]
39
+ def cast_by
40
+ @options[:cast_by]
41
+ end
42
+
43
+ # @param [Object] value
44
+ # @return [Object] casted value
45
+ # @raise [ActiveEntity::CastError]
46
+ def cast!(value)
47
+ case
48
+ when type
49
+ cast_by_defined_type(value)
50
+ when cast_by
51
+ cast_by.call(value)
52
+ else
53
+ value
14
54
  end
15
55
  end
16
56
 
17
- def attributes
18
- Hash[defined_attributes.keys.map {|name| [name.to_s, public_send(name)] }]
57
+ private
58
+
59
+ def cast_by_defined_type(value)
60
+ procedure = ActiveEntity::ConversionDefinitions.get(type)
61
+
62
+ if procedure
63
+ procedure.call(value)
64
+ else
65
+ raise ConfigurationError.new("Can't find casting procedure for `#{type}`")
66
+ end
19
67
  end
20
68
  end
21
69
  end
@@ -0,0 +1,10 @@
1
+ module ActiveEntity
2
+ module Coercion
3
+ include Typecasting
4
+
5
+ def initialize(*)
6
+ super
7
+ cast
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,62 @@
1
+ module ActiveEntity
2
+ class ConversionDefinitions
3
+ class << self
4
+ # @param [Class, Symbol, String] type
5
+ # @return [Proc]
6
+ def get(type)
7
+ type_casting_procs.get(type)
8
+ end
9
+
10
+ # @param [Class, Symbol, String] type
11
+ # @return [Proc]
12
+ def set(type, &block)
13
+ type_casting_procs.set(type, &block)
14
+ end
15
+
16
+ # Run block with new type_casting_procs. For test purpose.
17
+ # @return [nil]
18
+ def run_with_new_then_restore
19
+ old, @type_casting_procs = type_casting_procs, MapWithNormalizing.new
20
+ yield
21
+ @type_casting_procs = old
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ def type_casting_procs
28
+ @type_casting_procs ||= MapWithNormalizing.new
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ class MapWithNormalizing
35
+ def initialize
36
+ @map = {}
37
+ end
38
+
39
+ def get(type)
40
+ @map[normalize_type(type)]
41
+ end
42
+
43
+ def set(type, &block)
44
+ # TODO: block arity check
45
+ @map[normalize_type(type)] = block
46
+ end
47
+
48
+ def normalize_type(type)
49
+ case type
50
+ when Class
51
+ type.to_s
52
+ when String
53
+ type
54
+ when Symbol
55
+ type.to_s
56
+ else
57
+ raise ArgumentError.new("`type` must be a Class or String or Symbol, but #{type.class}")
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,57 @@
1
+ ActiveEntity::ConversionDefinitions.set(:Boolean) do |value|
2
+ case value
3
+ when 'true'
4
+ true
5
+ when 'false'
6
+ false
7
+ else
8
+ raise ActiveEntity::CastError.build(:Boolean, value)
9
+ end
10
+ end
11
+
12
+ ActiveEntity::ConversionDefinitions.set(Integer) do |value|
13
+ begin
14
+ Integer(value)
15
+ rescue
16
+ raise ActiveEntity::CastError.build(Integer, value)
17
+ end
18
+ end
19
+
20
+ ActiveEntity::ConversionDefinitions.set(Float) do |value|
21
+ begin
22
+ Float(value)
23
+ rescue
24
+ raise ActiveEntity::CastError.build(Float, value)
25
+ end
26
+ end
27
+
28
+ ActiveEntity::ConversionDefinitions.set(:'ActiveSupport::TimeWithZone') do |value|
29
+ begin
30
+ case value
31
+ when Fixnum, Float
32
+ Time.zone.at(value)
33
+ when String
34
+ Time.zone.parse(value)
35
+ else
36
+ raise ActiveEntity::CastError.build(:'ActiveSupport::TimeWithZone', value)
37
+ end
38
+ rescue ArgumentError
39
+ raise ActiveEntity::CastError.build(:'ActiveSupport::TimeWithZone', value)
40
+ end
41
+ end
42
+
43
+ ActiveEntity::ConversionDefinitions.set(String) do |value|
44
+ if value.respond_to? :to_s
45
+ value.to_s
46
+ else
47
+ raise ActiveEntity::CastError.build(String, value)
48
+ end
49
+ end
50
+
51
+ ActiveEntity::ConversionDefinitions.set(Symbol) do |value|
52
+ if value.respond_to? :to_sym
53
+ value.to_sym
54
+ else
55
+ raise ActiveEntity::CastError.build(Symbol, value)
56
+ end
57
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveEntity
2
+ class Error < StandardError
3
+ end
4
+
5
+ class CastError < Error
6
+ def self.build(type, value)
7
+ new("Can't type cast #{value.inspect} as #{type}")
8
+ end
9
+ end
10
+
11
+ class ConfigurationError < Error
12
+ end
13
+ end
@@ -3,8 +3,7 @@ module ActiveEntity
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- class_attribute :identity_attributes,
7
- instance_writer: false, instance_predicate: false
6
+ class_attribute :identity_attributes, instance_writer: false, instance_predicate: false
8
7
  self.identity_attributes = []
9
8
  end
10
9
 
@@ -14,17 +13,14 @@ module ActiveEntity
14
13
 
15
14
  define_method(:==) do |other|
16
15
  return false unless self.class === other
17
-
18
- names.all? do |name|
19
- public_send(name) == other.public_send(name)
20
- end
16
+ names.all? {|name| public_send(name) == other.public_send(name) }
21
17
  end
22
18
  end
23
19
  end
24
20
 
25
21
  # For ActiveModel::Conversion
26
22
  def to_key
27
- indentity_attributes.empty? ? nil : identity_attributes
23
+ identity_attributes.empty? ? nil : identity_attributes.map {|name| public_send(name) }
28
24
  end
29
25
  end
30
26
  end
@@ -0,0 +1,37 @@
1
+ module ActiveEntity
2
+ module Typecasting
3
+ # Try to cast attribute values. When fails to cast, raises the error and
4
+ # rollbacks all values.
5
+ #
6
+ # @return [nil]
7
+ # @raise [ActiveEntity::CastError]
8
+ def cast!
9
+ casted_values = defined_attributes.map do |name, attr|
10
+ value = public_send(name)
11
+ next [name, nil] if value.nil?
12
+ [name, attr.cast!(value)]
13
+ end
14
+
15
+ casted_values.each {|name, casted| public_send("#{name}=", casted) }
16
+ nil
17
+ end
18
+
19
+ # Try to cast attribute values. When fails to cast, ignores error and left
20
+ # attribute value as original one.
21
+ #
22
+ # @return [nil]
23
+ def cast
24
+ defined_attributes.each do |name, attr|
25
+ value = public_send(name)
26
+ next if value.nil?
27
+
28
+ begin
29
+ casted = attr.cast!(value)
30
+ public_send("#{name}=", casted)
31
+ rescue ActiveEntity::CastError
32
+ end
33
+ end
34
+ nil
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module ActiveEntity
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveEntity::Accessor do
4
+ let(:test_class) do
5
+ Class.new do
6
+ include ActiveModel::Model
7
+ include ActiveEntity::Accessor
8
+
9
+ attribute :name, type: String
10
+ attribute :age, type: String
11
+ end
12
+ end
13
+
14
+ let(:test_attributes) { { name: 'Alice', age: 1 } }
15
+
16
+ describe '.defined_attributes' do
17
+ subject { test_class.defined_attributes }
18
+ it { is_expected.to be_kind_of(Hash) }
19
+
20
+ it 'returns all defined attributes' do
21
+ expect(subject.keys).to eq(%i(name age))
22
+ expect(subject[:name]).to be_kind_of(ActiveEntity::Attribute)
23
+ expect(subject[:age]).to be_kind_of(ActiveEntity::Attribute)
24
+ end
25
+ end
26
+
27
+ describe '#initialize' do
28
+ subject { test_class.new(test_attributes) }
29
+
30
+ it 'assigns attributes' do
31
+ expect(subject.name).to eq('Alice')
32
+ expect(subject.age).to eq(1)
33
+ end
34
+ end
35
+
36
+ describe '#attributes' do
37
+ subject { test_class.new(test_attributes).attributes }
38
+
39
+ it { is_expected.to be_kind_of(Hash) }
40
+
41
+ it "returns all attribute's name-value pairs" do
42
+ is_expected.to eq('name' => 'Alice', 'age' => 1)
43
+ end
44
+ end
45
+ end
@@ -1,43 +1,83 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe ActiveEntity::Attribute do
4
- let(:test_class) do
5
- Class.new do
6
- include ActiveModel::Model
7
- include ActiveEntity::Attribute
4
+ describe 'typecasting' do
5
+ context 'with type option' do
6
+ let(:casting_proc) do
7
+ -> (value) {
8
+ begin
9
+ Integer(value)
10
+ rescue
11
+ raise ActiveEntity::CastError.build(Integer, value)
12
+ end
13
+ }
14
+ end
8
15
 
9
- attribute :name
10
- attribute :age
11
- end
12
- end
16
+ before do
17
+ allow(ActiveEntity::ConversionDefinitions).to receive(:get).
18
+ with(Integer).and_return(casting_proc)
19
+ end
13
20
 
14
- let(:test_attributes) { { name: 'Alice', age: 1 } }
21
+ let(:attr) { ActiveEntity::Attribute.new(:user_id, type: Integer) }
15
22
 
16
- describe '.defined_attributes' do
17
- subject { test_class.defined_attributes }
18
- it { is_expected.to be_kind_of(Hash) }
23
+ context 'with valid value' do
24
+ it 'converts given value' do
25
+ expect(attr.cast!('12')).to be_a(Integer)
26
+ expect(attr.cast!('12')).to eq(12)
27
+ end
28
+ end
19
29
 
20
- it 'returns all defined attributes' do
21
- is_expected.to eq(name: {}, age: {})
22
- end
23
- end
30
+ context 'with invalid value' do
31
+ it 'raises ActiveEntity::CastError' do
32
+ expect { attr.cast!('a') }.to raise_error(ActiveEntity::CastError)
33
+ end
34
+ end
24
35
 
25
- describe '#initialize' do
26
- subject { test_class.new(test_attributes) }
36
+ context 'with procedure of type is not defined' do
37
+ before do
38
+ allow(ActiveEntity::ConversionDefinitions).to receive(:get).
39
+ with(:UnknownType).and_return(nil)
40
+ end
27
41
 
28
- it 'assigns attributes' do
29
- expect(subject.name).to eq('Alice')
30
- expect(subject.age).to eq(1)
42
+ let(:attr) { ActiveEntity::Attribute.new(:user_id, type: :UnknownType) }
43
+
44
+ it 'raises ConfigurationError' do
45
+ expect {
46
+ attr.cast!('12')
47
+ }.to raise_error(ActiveEntity::ConfigurationError, /UnknownType/)
48
+ end
49
+ end
31
50
  end
32
- end
33
51
 
34
- describe '#attributes' do
35
- subject { test_class.new(test_attributes).attributes }
52
+ context 'with cast_by option' do
53
+ let(:attr) do
54
+ ActiveEntity::Attribute.new(
55
+ :created_at,
56
+ cast_by: -> (value) do
57
+ case value
58
+ when Fixnum, Float
59
+ Time.at(value)
60
+ when String
61
+ Time.parse(value)
62
+ else
63
+ raise ActiveEntity::CastError.build(:Time, value)
64
+ end
65
+ end
66
+ )
67
+ end
68
+
69
+ it 'converts given value' do
70
+ expect(attr.cast!(1420081200)).to be_a(Time)
71
+ expect(attr.cast!('2015/01/01 12:00:00')).to be_a(Time)
72
+ end
73
+ end
36
74
 
37
- it { is_expected.to be_kind_of(Hash) }
75
+ context 'without typecasting options' do
76
+ let(:attr) { ActiveEntity::Attribute.new(:user_id) }
38
77
 
39
- it "returns all attribute's name-value pairs" do
40
- is_expected.to eq('name' => 'Alice', 'age' => 1)
78
+ it 'does nothing and returns original' do
79
+ expect(attr.cast!('12')).to eq('12')
80
+ end
41
81
  end
42
82
  end
43
83
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveEntity::Coercion do
4
+ let(:test_class) do
5
+ Class.new do
6
+ include ActiveModel::Model
7
+ include ActiveEntity::Accessor
8
+ include ActiveEntity::Coercion
9
+
10
+ attribute :name, type: String
11
+ attribute :age, type: Integer
12
+ end
13
+ end
14
+
15
+ it 'automatically typecasts attribute values' do
16
+ person = test_class.new(name: 'Alice', age: '1')
17
+ expect(person.name).to eq('Alice')
18
+ expect(person.age).to eq(1)
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveEntity::ConversionDefinitions do
4
+ around do |example|
5
+ described_class.run_with_new_then_restore { example.run }
6
+ end
7
+
8
+ describe '.set and .get' do
9
+ type_as_class = ActiveSupport::TimeWithZone
10
+ type_as_symbol = :'ActiveSupport::TimeWithZone'
11
+ type_as_string = 'ActiveSupport::TimeWithZone'
12
+
13
+ [type_as_class, type_as_symbol, type_as_string].permutation(2).each do |a, b|
14
+ context "when set with #{a.class} and get with #{b.class}" do
15
+ it 'returns valid procedure' do
16
+ described_class.set(a) {}
17
+ expect(described_class.get(b)).not_to be_nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ context 'when given invalid type' do
24
+ it 'raises ArgumentError' do
25
+ expect { described_class.set(1) }.to raise_error(ArgumentError)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'default type conversion definitions' do
4
+ shared_examples 'raising error' do
5
+ it 'raises ActiveEntity::CastError' do
6
+ expect { subject }.to raise_error(ActiveEntity::CastError, /#{type}/)
7
+ end
8
+ end
9
+
10
+ let(:procedure) { ActiveEntity::ConversionDefinitions.get(type) }
11
+ subject { procedure.call(value) }
12
+
13
+ describe 'Boolean conversion' do
14
+ let(:type) { :Boolean }
15
+
16
+ context 'with `true` string' do
17
+ let(:value) { 'true' }
18
+ it { is_expected.to eq(true) }
19
+ end
20
+
21
+ context 'with `false` string' do
22
+ let(:value) { 'false' }
23
+ it { is_expected.to eq(false) }
24
+ end
25
+
26
+ context 'with invalid value' do
27
+ let(:value) { 'True' }
28
+ include_examples 'raising error'
29
+ end
30
+ end
31
+
32
+ describe 'Integer conversion' do
33
+ let(:type) { :Integer }
34
+
35
+ context 'with valid value' do
36
+ let(:value) { '12' }
37
+ it { is_expected.to eq(12) }
38
+ end
39
+
40
+ context 'with invalid value' do
41
+ let(:value) { 'abc' }
42
+ include_examples 'raising error'
43
+ end
44
+ end
45
+
46
+ describe 'Float conversion' do
47
+ let(:type) { :Float }
48
+
49
+ context 'with valid value' do
50
+ let(:value) { '12.1' }
51
+ it { is_expected.to eq(12.1) }
52
+ end
53
+
54
+ context 'with invalid value' do
55
+ let(:value) { 'abc' }
56
+ include_examples 'raising error'
57
+ end
58
+ end
59
+
60
+ describe 'ActiveSupport::TimeWithZone conversion' do
61
+ around do |example|
62
+ back, Time.zone = Time.zone, 'UTC'
63
+ example.run
64
+ Time.zone = back
65
+ end
66
+
67
+ let(:type) { :'ActiveSupport::TimeWithZone' }
68
+
69
+ context 'with string expression' do
70
+ let(:value) { '2015/01/05 02:12:12' }
71
+ it { is_expected.to be_a(ActiveSupport::TimeWithZone) }
72
+ end
73
+
74
+ context 'with float expression' do
75
+ let(:value) { 1420081200.089 }
76
+ it { is_expected.to be_a(ActiveSupport::TimeWithZone) }
77
+ end
78
+
79
+ context 'with invalid string value' do
80
+ let(:value) { '1420081200.089' }
81
+ include_examples 'raising error'
82
+ end
83
+
84
+ context 'with invalid value' do
85
+ let(:value) { Time.now }
86
+ include_examples 'raising error'
87
+ end
88
+ end
89
+
90
+ describe 'String conversion' do
91
+ let(:type) { :String }
92
+
93
+ context 'with valid value' do
94
+ let(:value) { 12 }
95
+ it { is_expected.to eq('12') }
96
+ end
97
+
98
+ context 'with invalid value' do
99
+ let(:value) { Object.new.tap {|o| o.instance_eval { undef :to_s } } }
100
+ include_examples 'raising error'
101
+ end
102
+ end
103
+
104
+ describe 'Symbol conversion' do
105
+ let(:type) { :Symbol }
106
+
107
+ context 'with valid value' do
108
+ let(:value) { 'abc' }
109
+ it { is_expected.to eq(:abc) }
110
+ end
111
+
112
+ context 'with invalid value' do
113
+ let(:value) { 12 }
114
+ include_examples 'raising error'
115
+ end
116
+ end
117
+ end
@@ -41,4 +41,12 @@ RSpec.describe ActiveEntity::Identity do
41
41
  end
42
42
  end
43
43
  end
44
+
45
+ describe '#to_key' do
46
+ let(:person) { test_class.new('Alice', 1) }
47
+
48
+ it 'returns values of key' do
49
+ expect(person.to_key).to eq(['Alice', 1])
50
+ end
51
+ end
44
52
  end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveEntity::Typecasting do
4
+ let(:test_class) do
5
+ Class.new do
6
+ include ActiveModel::Model
7
+ include ActiveEntity::Accessor
8
+ include ActiveEntity::Typecasting
9
+
10
+ attribute :name, type: String
11
+ attribute :age, type: Integer
12
+ attribute :item_count, type: Integer
13
+ end
14
+ end
15
+
16
+ let(:person) { test_class.new(assigned_values) }
17
+ let(:assigned_values) { { name: 'Alice', age: '1', item_count: '2' } }
18
+
19
+ describe '#cast!' do
20
+ context 'when assigned value is valid' do
21
+ it 'conversions its attributes' do
22
+ person.cast!
23
+ expect(person.name).to eq('Alice')
24
+ expect(person.age).to eq(1)
25
+ expect(person.item_count).to eq(2)
26
+ end
27
+ end
28
+
29
+ context 'when assigned value is invalid' do
30
+ let(:assigned_values) { { name: 'Alice', age: '1', item_count: 'a' } }
31
+
32
+ it 'raises ActiveEntity::CastError' do
33
+ expect { person.cast! }.to raise_error(ActiveEntity::CastError)
34
+ end
35
+
36
+ it 'rollbacks all values' do
37
+ expect { person.cast! }.to raise_error(ActiveEntity::CastError)
38
+ expect(person.age).to eq('1')
39
+ end
40
+ end
41
+
42
+ context 'when assigned value is nil' do
43
+ let(:assigned_values) { { name: nil, age: '1', item_count: nil } }
44
+
45
+ it 'skips nil values' do
46
+ expect { person.cast! }.not_to raise_error
47
+ expect(person.name).to be_nil
48
+ expect(person.age).to eq(1)
49
+ expect(person.item_count).to be_nil
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#cast' do
55
+ context 'when assigned value is valid' do
56
+ it 'conversions its attributes' do
57
+ person.cast
58
+ expect(person.name).to eq('Alice')
59
+ expect(person.age).to eq(1)
60
+ expect(person.item_count).to eq(2)
61
+ end
62
+ end
63
+
64
+ context 'when assigned value is invalid' do
65
+ let(:assigned_values) { { name: 'Alice', age: '1', item_count: 'a' } }
66
+
67
+ it 'skips invalid value' do
68
+ person.cast
69
+ expect(person.name).to eq('Alice')
70
+ expect(person.age).to eq(1)
71
+ expect(person.item_count).to eq('a')
72
+ end
73
+ end
74
+ end
75
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_entity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taiki Ono
@@ -147,17 +147,29 @@ files:
147
147
  - ".gitignore"
148
148
  - ".rspec"
149
149
  - ".travis.yml"
150
+ - CHANGELOG.md
150
151
  - Gemfile
151
152
  - LICENSE.txt
152
153
  - README.md
153
154
  - Rakefile
154
155
  - active_entity.gemspec
155
156
  - lib/active_entity.rb
157
+ - lib/active_entity/accessor.rb
156
158
  - lib/active_entity/attribute.rb
159
+ - lib/active_entity/coercion.rb
160
+ - lib/active_entity/conversion_definitions.rb
161
+ - lib/active_entity/default_definitions.rb
162
+ - lib/active_entity/errors.rb
157
163
  - lib/active_entity/identity.rb
164
+ - lib/active_entity/typecasting.rb
158
165
  - lib/active_entity/version.rb
166
+ - spec/active_entity/accessor_spec.rb
159
167
  - spec/active_entity/attribute_spec.rb
168
+ - spec/active_entity/coercion_spec.rb
169
+ - spec/active_entity/conversion_definitions_spec.rb
170
+ - spec/active_entity/default_definitions_spec.rb
160
171
  - spec/active_entity/identity_spec.rb
172
+ - spec/active_entity/typecasting_spec.rb
161
173
  - spec/readme_spec.rb
162
174
  - spec/spec_helper.rb
163
175
  homepage: https://github.com/taiki45/active_entity
@@ -185,8 +197,13 @@ signing_key:
185
197
  specification_version: 4
186
198
  summary: An Active Model extention for entity.
187
199
  test_files:
200
+ - spec/active_entity/accessor_spec.rb
188
201
  - spec/active_entity/attribute_spec.rb
202
+ - spec/active_entity/coercion_spec.rb
203
+ - spec/active_entity/conversion_definitions_spec.rb
204
+ - spec/active_entity/default_definitions_spec.rb
189
205
  - spec/active_entity/identity_spec.rb
206
+ - spec/active_entity/typecasting_spec.rb
190
207
  - spec/readme_spec.rb
191
208
  - spec/spec_helper.rb
192
209
  has_rdoc: