active_entity 0.1.0 → 0.2.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 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: