fast_attributes_rails 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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +194 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +292 -0
- data/Rakefile +1 -0
- data/benchmarks/Gemfile +8 -0
- data/benchmarks/comparison.rb +56 -0
- data/fast_attributes.gemspec +30 -0
- data/lib/fast_attributes.rb +83 -0
- data/lib/fast_attributes/builder.rb +125 -0
- data/lib/fast_attributes/default_attributes.rb +33 -0
- data/lib/fast_attributes/type_cast.rb +81 -0
- data/lib/fast_attributes/version.rb +3 -0
- data/spec/fast_attributes/type_cast_spec.rb +170 -0
- data/spec/fast_attributes_spec.rb +423 -0
- data/spec/fixtures/classes.rb +177 -0
- data/spec/spec_helper.rb +18 -0
- metadata +159 -0
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/benchmarks/Gemfile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'fast_attributes'
|
3
|
+
require 'virtus'
|
4
|
+
require 'attrio'
|
5
|
+
|
6
|
+
ATTR_NAMES = [:attr0, :attr1, :attr2, :attr3, :attr4, :attr5, :attr6, :attr7, :attr8, :attr9]
|
7
|
+
|
8
|
+
class FastIntegers
|
9
|
+
extend FastAttributes
|
10
|
+
define_attributes initialize: true do
|
11
|
+
attribute *ATTR_NAMES, Integer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class AttrioIntegers
|
16
|
+
include Attrio
|
17
|
+
define_attributes do
|
18
|
+
ATTR_NAMES.each do |name|
|
19
|
+
attr name, Integer
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(attributes = {})
|
24
|
+
attributes.each do |attribute, value|
|
25
|
+
public_send("#{attribute}=", value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class VirtusIntegers
|
31
|
+
include Virtus.model
|
32
|
+
ATTR_NAMES.each do |name|
|
33
|
+
attribute name, Integer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Benchmark.ips do |x|
|
38
|
+
x.config(time: 1, warmup: 1)
|
39
|
+
|
40
|
+
integers = {attr0: 0, attr1: 1, attr2: 2, attr3: 3, attr4: 4, attr5: 5, attr6: 6, attr7: 7, attr8: 8, attr9: 9}
|
41
|
+
strings = {attr0: '0', attr1: '1', attr2: '2', attr3: '3', attr4: '4', attr5: '5', attr6: '6', attr7: '7', attr8: '8', attr9: '9'}
|
42
|
+
|
43
|
+
x.report('FastAttributes: without values ') { FastIntegers.new }
|
44
|
+
x.report('FastAttributes: integer values for integer attributes') { FastIntegers.new(integers) }
|
45
|
+
x.report('FastAttributes: string values for integer attributes ') { FastIntegers.new(strings) }
|
46
|
+
|
47
|
+
x.report('Attrio: without values ') { AttrioIntegers.new }
|
48
|
+
x.report('Attrio: integer values for integer attributes ') { AttrioIntegers.new(integers) }
|
49
|
+
x.report('Attrio: string values for integer attributes ') { AttrioIntegers.new(strings) }
|
50
|
+
|
51
|
+
x.report('Virtus: without values ') { VirtusIntegers.new }
|
52
|
+
x.report('Virtus: integer values for integer attributes ') { VirtusIntegers.new(integers) }
|
53
|
+
x.report('Virtus: string values for integer attributes ') { VirtusIntegers.new(strings) }
|
54
|
+
|
55
|
+
x.compare!
|
56
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fast_attributes/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'fast_attributes_rails'
|
8
|
+
spec.version = FastAttributes::VERSION
|
9
|
+
spec.authors = ['Damian Baćkowski']
|
10
|
+
spec.email = ['damianbackowski@gmail.com']
|
11
|
+
spec.summary = 'Fast attributes with data types'
|
12
|
+
spec.description = 'Fast attributes with data types'
|
13
|
+
spec.homepage = 'https://github.com/dbackowski/fast_attributes'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = '>= 1.9.2'
|
22
|
+
|
23
|
+
spec.add_dependency 'activesupport', '>= 3.0.0', '< 6.0'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
28
|
+
spec.add_development_dependency 'coveralls', '~> 0.7.0'
|
29
|
+
spec.add_development_dependency 'simplecov', '~> 0.8.2'
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
require 'fast_attributes/version'
|
5
|
+
require 'fast_attributes/builder'
|
6
|
+
require 'fast_attributes/type_cast'
|
7
|
+
require 'fast_attributes/default_attributes'
|
8
|
+
require 'active_support/core_ext/hash'
|
9
|
+
|
10
|
+
module FastAttributes
|
11
|
+
TRUE_VALUES = {true => nil, 1 => nil, '1' => nil, 't' => nil, 'T' => nil, 'true' => nil, 'TRUE' => nil, 'on' => nil, 'ON' => nil}
|
12
|
+
FALSE_VALUES = {false => nil, 0 => nil, '0' => nil, 'f' => nil, 'F' => nil, 'false' => nil, 'FALSE' => nil, 'off' => nil, 'OFF' => nil}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def type_casting
|
16
|
+
@type_casting ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_type_casting(klass)
|
20
|
+
type_casting[klass]
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_type_casting(klass, casting)
|
24
|
+
symbol = klass.name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym # DateTime => :date_time
|
25
|
+
type_cast symbol, klass do
|
26
|
+
from 'nil', to: 'nil'
|
27
|
+
from klass.name, to: '%s'
|
28
|
+
otherwise casting
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove_type_casting(klass)
|
33
|
+
type_casting.delete(klass)
|
34
|
+
end
|
35
|
+
|
36
|
+
def type_exists?(klass)
|
37
|
+
type_casting.has_key?(klass)
|
38
|
+
end
|
39
|
+
|
40
|
+
def type_cast(*types_or_classes, &block)
|
41
|
+
types_or_classes.each do |type_or_class|
|
42
|
+
type_cast = TypeCast.new(type_or_class)
|
43
|
+
type_cast.instance_eval(&block)
|
44
|
+
type_casting[type_or_class] = type_cast
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def define_attributes(options = {}, &block)
|
50
|
+
builder = Builder.new(self, options)
|
51
|
+
builder.instance_eval(&block)
|
52
|
+
builder.compile!
|
53
|
+
end
|
54
|
+
|
55
|
+
def attribute(*attributes, type)
|
56
|
+
builder = Builder.new(self)
|
57
|
+
builder.attribute *attributes, type
|
58
|
+
builder.compile!
|
59
|
+
end
|
60
|
+
|
61
|
+
set_type_casting String, 'String(%s)'
|
62
|
+
set_type_casting Integer, 'Integer(%s)'
|
63
|
+
set_type_casting Float, 'Float(%s)'
|
64
|
+
set_type_casting Array, 'Array(%s)'
|
65
|
+
set_type_casting Date, 'Date.parse(%s)'
|
66
|
+
set_type_casting Time, 'Time.parse(%s)'
|
67
|
+
set_type_casting DateTime, 'DateTime.parse(%s)'
|
68
|
+
set_type_casting BigDecimal, 'Float(%s);BigDecimal(%s.to_s)'
|
69
|
+
|
70
|
+
type_cast :boolean do
|
71
|
+
otherwise <<-EOS
|
72
|
+
if FastAttributes::TRUE_VALUES.has_key?(%s)
|
73
|
+
true
|
74
|
+
elsif FastAttributes::FALSE_VALUES.has_key?(%s)
|
75
|
+
false
|
76
|
+
elsif %s.nil?
|
77
|
+
nil
|
78
|
+
else
|
79
|
+
raise FastAttributes::TypeCast::InvalidValueError, %(Invalid value "\#{%s}" for attribute "%a" of type ":boolean")
|
80
|
+
end
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module FastAttributes
|
2
|
+
class UnsupportedTypeError < TypeError
|
3
|
+
end
|
4
|
+
|
5
|
+
class Builder
|
6
|
+
def initialize(klass, options = {})
|
7
|
+
@klass = klass
|
8
|
+
@options = options
|
9
|
+
@attributes = []
|
10
|
+
@methods = Module.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def attribute(*attributes, type, options)
|
14
|
+
unless options.is_a?(Hash)
|
15
|
+
(attributes ||= []) << type
|
16
|
+
type = options
|
17
|
+
options = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
unless FastAttributes.type_exists?(type)
|
21
|
+
raise UnsupportedTypeError, %(Unsupported attribute type "#{type.inspect}")
|
22
|
+
end
|
23
|
+
|
24
|
+
@attributes << [attributes, type, options || {}]
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile!
|
28
|
+
compile_getter
|
29
|
+
compile_setter
|
30
|
+
set_defaults
|
31
|
+
|
32
|
+
if @options[:initialize]
|
33
|
+
compile_initialize
|
34
|
+
end
|
35
|
+
|
36
|
+
if @options[:attributes]
|
37
|
+
compile_attributes(@options[:attributes])
|
38
|
+
end
|
39
|
+
|
40
|
+
include_methods
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def compile_getter
|
46
|
+
each_attribute do |attribute, *|
|
47
|
+
@methods.module_eval <<-EOS, __FILE__, __LINE__ + 1
|
48
|
+
def #{attribute} # def name
|
49
|
+
@#{attribute} # @name
|
50
|
+
end # end
|
51
|
+
EOS
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def compile_setter
|
56
|
+
each_attribute do |attribute, type, *|
|
57
|
+
type_cast = FastAttributes.get_type_casting(type)
|
58
|
+
method_body = type_cast.compile_method_body(attribute, 'value')
|
59
|
+
|
60
|
+
@methods.module_eval <<-EOS, __FILE__, __LINE__ + 1
|
61
|
+
def #{attribute}=(value)
|
62
|
+
@#{attribute} = #{method_body}
|
63
|
+
end
|
64
|
+
EOS
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def compile_initialize
|
69
|
+
attribute_string = if FastAttributes.default_attributes(@klass).empty?
|
70
|
+
"attributes"
|
71
|
+
else
|
72
|
+
"FastAttributes.default_attributes(self.class).merge(attributes)"
|
73
|
+
end
|
74
|
+
|
75
|
+
@methods.module_eval <<-EOS, __FILE__, __LINE__ + 1
|
76
|
+
def initialize(attributes = {})
|
77
|
+
#{attribute_string}.each do |name, value|
|
78
|
+
public_send("\#{name}=", value) if respond_to?("\#{name}=".to_sym)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
|
84
|
+
def compile_attributes(mode)
|
85
|
+
attributes = @attributes.flat_map(&:first)
|
86
|
+
prefix = case mode
|
87
|
+
when :accessors then ''
|
88
|
+
else '@'
|
89
|
+
end
|
90
|
+
|
91
|
+
attributes = attributes.map do |attribute|
|
92
|
+
"'#{attribute}' => #{prefix}#{attribute}"
|
93
|
+
end
|
94
|
+
|
95
|
+
@methods.module_eval <<-EOS, __FILE__, __LINE__ + 1
|
96
|
+
def attributes # def attributes
|
97
|
+
{#{attributes.join(', ')}}.with_indifferent_access # {'name' => @name, ...}
|
98
|
+
end # end
|
99
|
+
EOS
|
100
|
+
end
|
101
|
+
|
102
|
+
def include_methods
|
103
|
+
@methods.instance_eval <<-EOS, __FILE__, __LINE__ + 1
|
104
|
+
def inspect
|
105
|
+
'FastAttributes(#{@attributes.flat_map(&:first).join(', ')})'
|
106
|
+
end
|
107
|
+
EOS
|
108
|
+
@klass.send(:include, @methods)
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_defaults
|
112
|
+
each_attribute do |attribute, type, options|
|
113
|
+
FastAttributes.add_default_attribute(@klass, attribute, options[:default]) if options[:default]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def each_attribute
|
118
|
+
@attributes.each do |attributes, type, options = {}|
|
119
|
+
attributes.each do |attribute|
|
120
|
+
yield attribute, type, options
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module FastAttributes
|
2
|
+
class << self
|
3
|
+
SINGLETON_CLASSES = [::NilClass, ::TrueClass, ::FalseClass, ::Numeric, ::Symbol].freeze
|
4
|
+
|
5
|
+
def default_attributes(klass)
|
6
|
+
return {} unless (@default_attributes || {})[klass]
|
7
|
+
@default_attributes[klass].each_with_object({}) do |(attribute, value), memo|
|
8
|
+
memo[attribute] = if value.respond_to?(:call)
|
9
|
+
value.call
|
10
|
+
elsif cloneable?(value)
|
11
|
+
value.clone
|
12
|
+
else
|
13
|
+
value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_default_attribute(klass, attribute, value)
|
19
|
+
@default_attributes ||= {}
|
20
|
+
@default_attributes[klass] ||= {}
|
21
|
+
@default_attributes[klass][attribute] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def cloneable?(value)
|
25
|
+
case value
|
26
|
+
when *SINGLETON_CLASSES
|
27
|
+
false
|
28
|
+
else
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module FastAttributes
|
2
|
+
class TypeCast
|
3
|
+
class UnknownTypeCastingError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidValueError < TypeError
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(type)
|
10
|
+
@type = type
|
11
|
+
@if_conditions = []
|
12
|
+
@else_condition = %q(raise FastAttributes::TypeCast::UnknownTypeCastingError, 'Type casting is not defined')
|
13
|
+
@rescue_conditions = nil
|
14
|
+
@default_rescue = %(raise FastAttributes::TypeCast::InvalidValueError, %(Invalid value "\#{%s}" for attribute "%a" of type "#{@type}"))
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def escape_template(template, attribute_name, argument_name)
|
19
|
+
template.gsub(/%+a|%+s/) do |match|
|
20
|
+
match.each_char.each_slice(2).map do |placeholder|
|
21
|
+
case placeholder
|
22
|
+
when %w[% a] then attribute_name
|
23
|
+
when %w[% s] then argument_name
|
24
|
+
when %w[% %] then '%'
|
25
|
+
else placeholder.join
|
26
|
+
end
|
27
|
+
end.join
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def from(condition, options = {})
|
33
|
+
@if_conditions << [condition, options[:to]]
|
34
|
+
end
|
35
|
+
|
36
|
+
def otherwise(else_condition)
|
37
|
+
@else_condition = else_condition
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_error(error, options = {})
|
41
|
+
@rescue_conditions ||=[]
|
42
|
+
@rescue_conditions << [error, options[:act]]
|
43
|
+
end
|
44
|
+
|
45
|
+
def type_casting_template
|
46
|
+
@type_casting_template ||= begin
|
47
|
+
if @if_conditions.any?
|
48
|
+
conditions = @if_conditions.map do |from, to|
|
49
|
+
"when #{from}\n" +
|
50
|
+
" #{to}\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
"case %s\n" +
|
54
|
+
conditions.join +
|
55
|
+
"else\n" +
|
56
|
+
" #{@else_condition}\n" +
|
57
|
+
"end"
|
58
|
+
else
|
59
|
+
@else_condition
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def rescue_template
|
65
|
+
rescues = @rescue_conditions || [['', @default_rescue]]
|
66
|
+
rescues.map do |error, action|
|
67
|
+
"rescue #{error} => e\n" +
|
68
|
+
" #{action}"
|
69
|
+
end.join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
def compile_method_body(attribute_name, argument_name)
|
73
|
+
method_body = "begin\n" +
|
74
|
+
" #{type_casting_template}\n" +
|
75
|
+
"#{rescue_template}\n" +
|
76
|
+
"end"
|
77
|
+
|
78
|
+
self.class.escape_template(method_body, attribute_name, argument_name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FastAttributes::TypeCast do
|
4
|
+
describe '.escape_template' do
|
5
|
+
it 'replaces placeholder with proper values' do
|
6
|
+
template = '% %s %%s %%%s %%%%s % %a %%a %%%a %%%%a'
|
7
|
+
escaped = FastAttributes::TypeCast.escape_template(template, 'price', 'arg')
|
8
|
+
expect(escaped).to eq('% arg %s %arg %%s % price %a %price %%a')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#type_casting_template' do
|
13
|
+
let(:type_cast) { FastAttributes::TypeCast.new(String) }
|
14
|
+
|
15
|
+
describe 'without any conditions' do
|
16
|
+
it 'return exception' do
|
17
|
+
expect(type_cast.type_casting_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
18
|
+
raise FastAttributes::TypeCast::UnknownTypeCastingError, 'Type casting is not defined'
|
19
|
+
EOS
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'when one rule is defined' do
|
24
|
+
before do
|
25
|
+
type_cast.from 'nil', to: 'nil'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns one when statement' do
|
29
|
+
expect(type_cast.type_casting_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
30
|
+
case %s
|
31
|
+
when nil
|
32
|
+
nil
|
33
|
+
else
|
34
|
+
raise FastAttributes::TypeCast::UnknownTypeCastingError, 'Type casting is not defined'
|
35
|
+
end
|
36
|
+
EOS
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'when three rules are defined' do
|
41
|
+
before do
|
42
|
+
type_cast.from 'String', to: 'String(%s)'
|
43
|
+
type_cast.from 'Array', to: 'Array(%s)'
|
44
|
+
type_cast.from 'Integer', to: 'Integer(%s)'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns three when statements' do
|
48
|
+
expect(type_cast.type_casting_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
49
|
+
case %s
|
50
|
+
when String
|
51
|
+
String(%s)
|
52
|
+
when Array
|
53
|
+
Array(%s)
|
54
|
+
when Integer
|
55
|
+
Integer(%s)
|
56
|
+
else
|
57
|
+
raise FastAttributes::TypeCast::UnknownTypeCastingError, 'Type casting is not defined'
|
58
|
+
end
|
59
|
+
EOS
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'when 2 rules and otherwise condition are defined' do
|
64
|
+
before do
|
65
|
+
type_cast.from 'Date', to: 'Date.parse(%s)'
|
66
|
+
type_cast.from 'Time', to: 'Time.parse(%s)'
|
67
|
+
type_cast.otherwise 'Float(%s)'
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'returns 2 when statements and overrides default else condition' do
|
71
|
+
expect(type_cast.type_casting_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
72
|
+
case %s
|
73
|
+
when Date
|
74
|
+
Date.parse(%s)
|
75
|
+
when Time
|
76
|
+
Time.parse(%s)
|
77
|
+
else
|
78
|
+
Float(%s)
|
79
|
+
end
|
80
|
+
EOS
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'when only otherwise rule is defined' do
|
85
|
+
before do
|
86
|
+
type_cast.otherwise '42 * %s'
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns otherwise statement only' do
|
90
|
+
expect(type_cast.type_casting_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
91
|
+
42 * %s
|
92
|
+
EOS
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#rescue_template' do
|
98
|
+
let(:type_cast) { FastAttributes::TypeCast.new(Float) }
|
99
|
+
|
100
|
+
describe 'when on_error is not defined' do
|
101
|
+
it 'raises default error message' do
|
102
|
+
expect(type_cast.rescue_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
103
|
+
rescue => e
|
104
|
+
raise FastAttributes::TypeCast::InvalidValueError, %(Invalid value "\#{%s}" for attribute "%a" of type "Float")
|
105
|
+
EOS
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'when on_error is defined' do
|
110
|
+
before do
|
111
|
+
type_cast.on_error 'ArgumentError', act: '"%s" + "%a"'
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'overrides default action' do
|
115
|
+
expect(type_cast.rescue_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
116
|
+
rescue ArgumentError => e
|
117
|
+
"%s" + "%a"
|
118
|
+
EOS
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'when several on_error methods is called' do
|
123
|
+
before do
|
124
|
+
type_cast.on_error 'ArgumentError', act: '0'
|
125
|
+
type_cast.on_error 'StandardError', act: '1'
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'generates several rescue conditions' do
|
129
|
+
expect(type_cast.rescue_template.gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
130
|
+
rescue ArgumentError => e
|
131
|
+
0
|
132
|
+
rescue StandardError => e
|
133
|
+
1
|
134
|
+
EOS
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe '#compile_method_body' do
|
140
|
+
let(:type_cast) { FastAttributes::TypeCast.new(Float) }
|
141
|
+
|
142
|
+
before do
|
143
|
+
type_cast.from 'nil', to: 'nil'
|
144
|
+
type_cast.from 'Float', to: '%s'
|
145
|
+
type_cast.otherwise 'Float(%s)'
|
146
|
+
type_cast.on_error 'ArgumentError', act: 'raise "a %s %a"'
|
147
|
+
type_cast.on_error 'StandardError', act: 'raise "b %s %a"'
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
it 'generates type casting method' do
|
152
|
+
expect(type_cast.compile_method_body('price', 'argument').gsub(' ', '')).to eq <<-EOS.gsub(' ', '').chomp
|
153
|
+
begin
|
154
|
+
case argument
|
155
|
+
when nil
|
156
|
+
nil
|
157
|
+
when Float
|
158
|
+
argument
|
159
|
+
else
|
160
|
+
Float(argument)
|
161
|
+
end
|
162
|
+
rescue ArgumentError => e
|
163
|
+
raise "a argument price"
|
164
|
+
rescue StandardError => e
|
165
|
+
raise "b argument price"
|
166
|
+
end
|
167
|
+
EOS
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|