attr_typecastable 0.0.1
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/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/Rakefile +7 -0
- data/attr_typecastable.gemspec +26 -0
- data/lib/attr_typecastable.rb +76 -0
- data/lib/attr_typecastable/initialize_hook.rb +17 -0
- data/lib/attr_typecastable/reflection.rb +20 -0
- data/lib/attr_typecastable/types.rb +48 -0
- data/lib/attr_typecastable/types/array.rb +28 -0
- data/lib/attr_typecastable/types/base.rb +24 -0
- data/lib/attr_typecastable/types/big_decimal.rb +31 -0
- data/lib/attr_typecastable/types/boolean.rb +28 -0
- data/lib/attr_typecastable/types/date.rb +21 -0
- data/lib/attr_typecastable/types/date_time.rb +24 -0
- data/lib/attr_typecastable/types/exception.rb +9 -0
- data/lib/attr_typecastable/types/float.rb +18 -0
- data/lib/attr_typecastable/types/integer.rb +18 -0
- data/lib/attr_typecastable/types/object.rb +14 -0
- data/lib/attr_typecastable/types/rational.rb +18 -0
- data/lib/attr_typecastable/types/string.rb +18 -0
- data/lib/attr_typecastable/types/symbol.rb +18 -0
- data/lib/attr_typecastable/types/time.rb +20 -0
- data/lib/attr_typecastable/version.rb +3 -0
- data/spec/attr_typecastable_spec.rb +226 -0
- data/spec/performance_check.rb +14 -0
- data/spec/spec_helper.rb +9 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d6d4f8c8175714df9ce86adf7985697d25c937a7
|
4
|
+
data.tar.gz: 30969bfe24b9062d8b0d74db2faa4a8fb7c82b20
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1e206d63240dc70bdb5241706a31c03bd2da6ad69db558ae4f07ede3ceae278ac5568fab6d39280b521ffd6143ff24a5a25ab6c3e57a9733a28f95b7d6a7a3af
|
7
|
+
data.tar.gz: a9f4453895c2fb5f5af9f0f76cccb5408731749b0550b2c8a5daaa59ea32b11e037ddf61ef1879978014919830d8495d2ad3bb39f4d221c5266d39412ac47b89
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in attr_typecastable.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rspec-power_assert', github: 'joker1007/rspec-power_assert'
|
7
|
+
gem 'power_assert', github: 'k-tsj/power_assert'
|
8
|
+
gem 'benchmark-ips'
|
9
|
+
gem 'seeing_is_believing'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 joker1007
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# attr\_typecastable
|
2
|
+
[](https://travis-ci.org/joker1007/attr_typecastable)
|
3
|
+
[](https://coveralls.io/r/joker1007/attr_typecastable)
|
4
|
+
[](https://codeclimate.com/github/joker1007/attr_typecastable)
|
5
|
+
|
6
|
+
attr\_accessor with typecast feature.
|
7
|
+
|
8
|
+
## Requirements
|
9
|
+
- Ruby 2.0 or later
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'attr_typecastable'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install attr_typecastable
|
26
|
+
|
27
|
+
## Default Typecasters
|
28
|
+
- Object (no typecasting)
|
29
|
+
- String
|
30
|
+
- Integer
|
31
|
+
- Float
|
32
|
+
- Rational
|
33
|
+
- BigDecimal
|
34
|
+
- Boolean
|
35
|
+
- Time
|
36
|
+
- Date
|
37
|
+
- DateTime
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'attr_typecastable'
|
43
|
+
|
44
|
+
# Custome typecaster definition
|
45
|
+
class CastToMoney < AttrTypecastable::Types::Base
|
46
|
+
private
|
47
|
+
|
48
|
+
# must define #do_typecast method
|
49
|
+
def do_typecast(value)
|
50
|
+
# can use @options
|
51
|
+
# @options is hash given to `typed_attr_accessor`
|
52
|
+
return value if value.is_a?(Money)
|
53
|
+
|
54
|
+
if value.is_a?(Integer)
|
55
|
+
Money.new(value)
|
56
|
+
else
|
57
|
+
raise AttrTypecastable::Types::CastError, "Cannot convert to Money"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class User
|
63
|
+
# define typed_attr_accessor and initialize hook
|
64
|
+
include AttrTypecastable
|
65
|
+
|
66
|
+
typed_attr_accessor :name, String
|
67
|
+
|
68
|
+
# String typecaster uses #to_s
|
69
|
+
typed_attr_accessor :default_name, String, default: "Foo"
|
70
|
+
typed_attr_accessor :not_nil_name, String, allow_nil: false, default: ""
|
71
|
+
|
72
|
+
# Date typecaster uses #to_date
|
73
|
+
typed_attr_accessor :birthday, Date
|
74
|
+
|
75
|
+
# Time typecaster uses #to_time
|
76
|
+
typed_attr_accessor :birthday_time, Time
|
77
|
+
|
78
|
+
# DateTime typecaster uses #to_time and #to_datetime
|
79
|
+
typed_attr_accessor :birthday_datetime, DateTime
|
80
|
+
|
81
|
+
# Integer typecaster uses #to_i
|
82
|
+
typed_attr_accessor :age, Integer
|
83
|
+
|
84
|
+
# Float typecaster uses #to_f
|
85
|
+
typed_attr_accessor :bmi, Float
|
86
|
+
|
87
|
+
# Custom Typecaster
|
88
|
+
typed_attr_accessor :property, CastToMoney
|
89
|
+
|
90
|
+
# Boolean typecaster uses `#===` method in order to check whether value is true or false.
|
91
|
+
typed_attr_accessor :adult, Boolean # default true_values: ["true", 1], false_values: ["false", 0]
|
92
|
+
typed_attr_accessor :admin, Boolean, true_value: ["yes"]
|
93
|
+
typed_attr_accessor :active, Boolean, true_value: [/true/i] # can use Regexp
|
94
|
+
|
95
|
+
def initialize(name: nil, default_name: nil)
|
96
|
+
self.name = name
|
97
|
+
self.default_name = default_name
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
user = User.new
|
102
|
+
user.name # => nil
|
103
|
+
user.default_name # => "Foo"
|
104
|
+
|
105
|
+
user.name = 1
|
106
|
+
user.name # => "1"
|
107
|
+
|
108
|
+
user.birthday = "1990-10-1"
|
109
|
+
user.birthday # => Mon, 01 Oct 1990
|
110
|
+
|
111
|
+
user.adult = "true"
|
112
|
+
user.adult # => true
|
113
|
+
|
114
|
+
user.adult = "false"
|
115
|
+
user.adult # => false
|
116
|
+
|
117
|
+
user.adult = "other"
|
118
|
+
user.adult # => true
|
119
|
+
|
120
|
+
user.admin = "yes"
|
121
|
+
user.admin # => true
|
122
|
+
|
123
|
+
user.active = "TrUe"
|
124
|
+
user.active # => true
|
125
|
+
|
126
|
+
user2 = User.new(default_name: "joker")
|
127
|
+
user2.default_name # => "joker"
|
128
|
+
```
|
129
|
+
|
130
|
+
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
1. Fork it ( https://github.com/joker1007/attr_typecastable/fork )
|
134
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
135
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
136
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
137
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'attr_typecastable/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "attr_typecastable"
|
8
|
+
spec.version = AttrTypecastable::VERSION
|
9
|
+
spec.authors = ["joker1007"]
|
10
|
+
spec.email = ["kakyoin.hierophant@gmail.com"]
|
11
|
+
spec.summary = %q{attr_accessor with typecast feature.}
|
12
|
+
spec.description = %q{attr_accessor with typecast feature.}
|
13
|
+
spec.homepage = "https://github.com/joker1007/attr_typecastable"
|
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.add_runtime_dependency "activesupport", ">= 3.2"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "coveralls"
|
26
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "attr_typecastable/version"
|
2
|
+
require "attr_typecastable/types"
|
3
|
+
require "attr_typecastable/initialize_hook"
|
4
|
+
require "attr_typecastable/reflection"
|
5
|
+
|
6
|
+
require "active_support/concern"
|
7
|
+
require "active_support/core_ext/class"
|
8
|
+
require "active_support/core_ext/hash"
|
9
|
+
|
10
|
+
module AttrTypecastable
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
included do
|
14
|
+
class_attribute(:typed_attr_reflections)
|
15
|
+
self.typed_attr_reflections = {}
|
16
|
+
prepend InitializeHook
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
DEFAULT_OPTIONS = {
|
21
|
+
allow_nil: true,
|
22
|
+
cast_method: true,
|
23
|
+
reset_method: true,
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def typed_attr_accessor(attribute_name, typecaster_name = Object, **options)
|
27
|
+
attribute_name = attribute_name.to_sym
|
28
|
+
options = DEFAULT_OPTIONS.merge(options)
|
29
|
+
must_have_default_when_disallow_nil(options)
|
30
|
+
|
31
|
+
typecaster = Types.typecaster_for(typecaster_name || Object).new(options)
|
32
|
+
typed_attr_reflections[attribute_name] = Reflection.new(attribute_name, typecaster, options)
|
33
|
+
|
34
|
+
attr_reader attribute_name
|
35
|
+
|
36
|
+
define_typed_attr_writer(attribute_name)
|
37
|
+
define_cast_attribute(attribute_name) if options[:cast_method]
|
38
|
+
define_reset_attribute(attribute_name) if options[:reset_method]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def define_typed_attr_writer(attribute_name)
|
44
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
45
|
+
def #{attribute_name}=(value)
|
46
|
+
typecaster = self.class.typed_attr_reflections[:#{attribute_name}].typecaster
|
47
|
+
@#{attribute_name} = typecaster.typecast(value)
|
48
|
+
end
|
49
|
+
RUBY
|
50
|
+
end
|
51
|
+
|
52
|
+
def define_cast_attribute(attribute_name)
|
53
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
54
|
+
def cast_#{attribute_name}!
|
55
|
+
value = @#{attribute_name}
|
56
|
+
self.#{attribute_name} = value
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
end
|
60
|
+
|
61
|
+
def define_reset_attribute(attribute_name, **options)
|
62
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
63
|
+
def reset_#{attribute_name}!
|
64
|
+
default = self.class.typed_attr_reflections[:#{attribute_name}].default
|
65
|
+
value = default.respond_to?(:call) ? default.call(self, :#{attribute_name}) : default
|
66
|
+
self.#{attribute_name} = value
|
67
|
+
end
|
68
|
+
RUBY
|
69
|
+
end
|
70
|
+
|
71
|
+
def must_have_default_when_disallow_nil(options)
|
72
|
+
raise "Need `default` option when allow_nil is false" \
|
73
|
+
if options[:allow_nil].! && options[:default].nil?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module AttrTypecastable
|
2
|
+
module InitializeHook
|
3
|
+
def initialize(*)
|
4
|
+
super
|
5
|
+
|
6
|
+
self.class.typed_attr_reflections.select {|_, r| r.has_default?}.each do |attr, reflection|
|
7
|
+
current = send("#{attr}")
|
8
|
+
next unless current.nil?
|
9
|
+
|
10
|
+
default = reflection.default
|
11
|
+
|
12
|
+
v = default.respond_to?(:call) ? default.call(self, attr) : default
|
13
|
+
send("#{attr}=", v)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AttrTypecastable
|
2
|
+
class Reflection
|
3
|
+
attr_reader :attribute_name, :typecaster, :options
|
4
|
+
|
5
|
+
def initialize(attribute_name, typecaster, **options)
|
6
|
+
@attribute_name = attribute_name
|
7
|
+
@typecaster = typecaster
|
8
|
+
@options = options
|
9
|
+
freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def default
|
13
|
+
@options[:default]
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_default?
|
17
|
+
@options.key?(:default)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
require 'attr_typecastable/types/exception'
|
6
|
+
require 'attr_typecastable/types/string'
|
7
|
+
require 'attr_typecastable/types/symbol'
|
8
|
+
require 'attr_typecastable/types/integer'
|
9
|
+
require 'attr_typecastable/types/time'
|
10
|
+
require 'attr_typecastable/types/date'
|
11
|
+
require 'attr_typecastable/types/date_time'
|
12
|
+
require 'attr_typecastable/types/float'
|
13
|
+
require 'attr_typecastable/types/rational'
|
14
|
+
require 'attr_typecastable/types/big_decimal'
|
15
|
+
require 'attr_typecastable/types/boolean'
|
16
|
+
require 'attr_typecastable/types/object'
|
17
|
+
require 'attr_typecastable/types/array'
|
18
|
+
|
19
|
+
unless defined?(::Boolean)
|
20
|
+
class Boolean; end
|
21
|
+
end
|
22
|
+
|
23
|
+
module AttrTypecastable
|
24
|
+
module Types
|
25
|
+
BASIC_TYPES = {
|
26
|
+
::Object => AttrTypecastable::Types::Object,
|
27
|
+
::String => AttrTypecastable::Types::String,
|
28
|
+
::Symbol => AttrTypecastable::Types::Symbol,
|
29
|
+
::Integer => AttrTypecastable::Types::Integer,
|
30
|
+
::Time => AttrTypecastable::Types::Time,
|
31
|
+
::Date => AttrTypecastable::Types::Date,
|
32
|
+
::DateTime => AttrTypecastable::Types::DateTime,
|
33
|
+
::Float => AttrTypecastable::Types::Float,
|
34
|
+
::Rational => AttrTypecastable::Types::Rational,
|
35
|
+
::BigDecimal => AttrTypecastable::Types::BigDecimal,
|
36
|
+
::Boolean => AttrTypecastable::Types::Boolean,
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def typecaster_for(typecaster, **options)
|
41
|
+
const = typecaster.is_a?(::String) ? typecaster.constantize : typecaster
|
42
|
+
BASIC_TYPES.fetch(const, const)
|
43
|
+
rescue NameError
|
44
|
+
raise TypeCasterNotFound, "#{typecaster} is undefined"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
require "active_support/core_ext/class"
|
5
|
+
|
6
|
+
module AttrTypecastable
|
7
|
+
module Types
|
8
|
+
class ArrayBase < Base
|
9
|
+
class_attribute :inner_typecaster
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def do_typecast(value)
|
14
|
+
wrapped = Array(value)
|
15
|
+
wrapped.map { |v| inner_typecaster.typecast(v) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ArrayFactory
|
20
|
+
def self.build(typecaster_name, **options)
|
21
|
+
typecaster = Types.typecaster_for(typecaster_name).new(options)
|
22
|
+
klass = Class.new(ArrayBase)
|
23
|
+
klass.inner_typecaster = typecaster
|
24
|
+
klass
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AttrTypecastable
|
2
|
+
module Types
|
3
|
+
class Base
|
4
|
+
def initialize(**options)
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def typecast(value)
|
9
|
+
return nil if allow_nil_and_nil_value?(value)
|
10
|
+
do_typecast(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def do_typecast(value)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def allow_nil_and_nil_value?(value)
|
20
|
+
@options[:allow_nil] && value.nil?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
module AttrTypecastable
|
4
|
+
module Types
|
5
|
+
class BigDecimal < Base
|
6
|
+
def do_typecast(value)
|
7
|
+
require 'bigdecimal/util'
|
8
|
+
|
9
|
+
return value if value.is_a?(::BigDecimal)
|
10
|
+
|
11
|
+
if value.is_a?(::Rational)
|
12
|
+
if @options[:fig]
|
13
|
+
value.to_d(@options[:fig])
|
14
|
+
else
|
15
|
+
value.to_f.to_d
|
16
|
+
end
|
17
|
+
elsif value.is_a?(::Float)
|
18
|
+
if @options[:precision]
|
19
|
+
value.to_d(@options[:precision])
|
20
|
+
else
|
21
|
+
value.to_d
|
22
|
+
end
|
23
|
+
elsif value.respond_to?(:to_d)
|
24
|
+
value.to_d
|
25
|
+
else
|
26
|
+
BigDecimal.new(value.to_s)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
module AttrTypecastable
|
5
|
+
module Types
|
6
|
+
class Boolean < Base
|
7
|
+
def initialize(**options)
|
8
|
+
super
|
9
|
+
@options[:true_values] ||= ["true", 1]
|
10
|
+
@options[:false_values] ||= ["false", 0]
|
11
|
+
end
|
12
|
+
|
13
|
+
def do_typecast(value)
|
14
|
+
return value if value.is_a?(::TrueClass) || value.is_a?(::FalseClass)
|
15
|
+
|
16
|
+
true_values = Array(@options[:true_values])
|
17
|
+
false_values = Array(@options[:false_values])
|
18
|
+
if true_values.any? {|v| v === value }
|
19
|
+
true
|
20
|
+
elsif false_values.any? {|v| v === value }
|
21
|
+
false
|
22
|
+
else
|
23
|
+
!!value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'active_support/core_ext/string'
|
6
|
+
|
7
|
+
module AttrTypecastable
|
8
|
+
module Types
|
9
|
+
class Date < Base
|
10
|
+
def do_typecast(value)
|
11
|
+
return value if value.is_a?(::Date)
|
12
|
+
|
13
|
+
if value.respond_to?(:to_date)
|
14
|
+
value.to_date
|
15
|
+
else
|
16
|
+
raise CastError, "value does not have `to_date` method"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'active_support/core_ext/string'
|
6
|
+
|
7
|
+
module AttrTypecastable
|
8
|
+
module Types
|
9
|
+
class DateTime < Base
|
10
|
+
def do_typecast(value)
|
11
|
+
return value if value.is_a?(::DateTime)
|
12
|
+
|
13
|
+
case value
|
14
|
+
when ::String
|
15
|
+
value.to_time.to_datetime
|
16
|
+
when ::Time, ::Date
|
17
|
+
value.to_datetime
|
18
|
+
else
|
19
|
+
raise CastError, "cannot convert to DateTime"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
module AttrTypecastable
|
5
|
+
module Types
|
6
|
+
class Float < Base
|
7
|
+
def do_typecast(value)
|
8
|
+
return value if value.is_a?(::Float)
|
9
|
+
|
10
|
+
if value.respond_to?(:to_f)
|
11
|
+
value.to_f
|
12
|
+
else
|
13
|
+
raise CastError, "value does not have `to_f` method"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
module AttrTypecastable
|
5
|
+
module Types
|
6
|
+
class Integer < Base
|
7
|
+
def do_typecast(value)
|
8
|
+
return value if value.is_a?(::Integer)
|
9
|
+
|
10
|
+
if value.respond_to?(:to_i)
|
11
|
+
value.to_i
|
12
|
+
else
|
13
|
+
raise CastError, "value does not have `to_i` method"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
module AttrTypecastable
|
5
|
+
module Types
|
6
|
+
class Rational < Base
|
7
|
+
def do_typecast(value)
|
8
|
+
return value if value.is_a?(::Rational)
|
9
|
+
|
10
|
+
if value.respond_to?(:to_r)
|
11
|
+
value.to_r
|
12
|
+
else
|
13
|
+
raise CastError, "value does not have `to_r` method"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
module AttrTypecastable
|
5
|
+
module Types
|
6
|
+
class String < Base
|
7
|
+
def do_typecast(value)
|
8
|
+
return value if value.is_a?(::String)
|
9
|
+
|
10
|
+
if value.respond_to?(:to_s)
|
11
|
+
value.to_s
|
12
|
+
else
|
13
|
+
raise CastError, "value does not have `to_s` method"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
module AttrTypecastable
|
5
|
+
module Types
|
6
|
+
class Symbol < Base
|
7
|
+
def do_typecast(value)
|
8
|
+
return value if value.is_a?(::Symbol)
|
9
|
+
|
10
|
+
if value.respond_to?(:to_sym)
|
11
|
+
value.to_sym
|
12
|
+
else
|
13
|
+
raise CastError, "value does not have `to_sym` method"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'attr_typecastable/types/exception'
|
2
|
+
require 'attr_typecastable/types/base'
|
3
|
+
|
4
|
+
require 'active_support/core_ext/string'
|
5
|
+
|
6
|
+
module AttrTypecastable
|
7
|
+
module Types
|
8
|
+
class Time < Base
|
9
|
+
def do_typecast(value)
|
10
|
+
return value if value.is_a?(::Time)
|
11
|
+
|
12
|
+
if value.respond_to?(:to_time)
|
13
|
+
value.to_time
|
14
|
+
else
|
15
|
+
raise CastError, "value does not have `to_time` method"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AttrTypecastable do
|
4
|
+
class Money
|
5
|
+
include AttrTypecastable
|
6
|
+
typed_attr_accessor :value, Integer
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
self.value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
return false unless other.is_a?(Money)
|
14
|
+
|
15
|
+
@value == other.value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class CastToMoney < AttrTypecastable::Types::Base
|
20
|
+
private
|
21
|
+
|
22
|
+
def do_typecast(value)
|
23
|
+
return value if value.is_a?(Money)
|
24
|
+
|
25
|
+
if value.is_a?(Integer)
|
26
|
+
Money.new(value)
|
27
|
+
else
|
28
|
+
raise AttrTypecastable::Types::CastError, "Cannot convert to Money"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class User
|
34
|
+
include AttrTypecastable
|
35
|
+
|
36
|
+
typed_attr_accessor :name, String
|
37
|
+
typed_attr_accessor :default_name, String, default: "Foo"
|
38
|
+
typed_attr_accessor :conditional_default, String, default: proc { |user|
|
39
|
+
"Hello #{user.name}"
|
40
|
+
}
|
41
|
+
typed_attr_accessor :not_nil_name, String, allow_nil: false, default: ""
|
42
|
+
typed_attr_accessor :role, Symbol
|
43
|
+
typed_attr_accessor :birthday, "Date"
|
44
|
+
typed_attr_accessor :birthday_time, Time
|
45
|
+
typed_attr_accessor :birthday_datetime, DateTime
|
46
|
+
typed_attr_accessor :age, Integer
|
47
|
+
typed_attr_accessor :bmi, Float
|
48
|
+
typed_attr_accessor :bmi_r, Rational
|
49
|
+
typed_attr_accessor :property, CastToMoney
|
50
|
+
typed_attr_accessor :debt, BigDecimal
|
51
|
+
typed_attr_accessor :debt_with_precision, BigDecimal, precision: 3
|
52
|
+
typed_attr_accessor :debt_with_fig, BigDecimal, fig: 5
|
53
|
+
typed_attr_accessor :adult, Boolean
|
54
|
+
typed_attr_accessor :admin, Boolean, true_value: ["yes"]
|
55
|
+
typed_attr_accessor :active, Boolean, true_value: [/true/i]
|
56
|
+
typed_attr_accessor :manager, Boolean, allow_nil: false, default: false
|
57
|
+
typed_attr_accessor :raw_value, nil, default: 1
|
58
|
+
typed_attr_accessor :raw_value2, Object
|
59
|
+
typed_attr_accessor :raw_value3, default: "raw_value3"
|
60
|
+
|
61
|
+
typed_attr_accessor :skills, AttrTypecastable::Types::ArrayFactory.build(String), default: []
|
62
|
+
typed_attr_accessor :friend_ids, AttrTypecastable::Types::ArrayFactory.build(Integer)
|
63
|
+
typed_attr_accessor :nested_array,
|
64
|
+
AttrTypecastable::Types::ArrayFactory.build(
|
65
|
+
AttrTypecastable::Types::ArrayFactory.build(Integer)
|
66
|
+
)
|
67
|
+
|
68
|
+
def initialize(name: nil, default_name: nil)
|
69
|
+
self.name = name
|
70
|
+
self.default_name = default_name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe ".typed_attr_accessor" do
|
75
|
+
it "defines attribute_accessor with typecasting" do
|
76
|
+
user = User.new
|
77
|
+
|
78
|
+
assert { user.default_name == "Foo" }
|
79
|
+
|
80
|
+
user.name = "joker"
|
81
|
+
assert { user.name == "joker" }
|
82
|
+
|
83
|
+
user.name = 10
|
84
|
+
assert { user.name == "10" }
|
85
|
+
|
86
|
+
user.name = nil
|
87
|
+
assert { user.name.nil? }
|
88
|
+
|
89
|
+
assert { user.not_nil_name == "" }
|
90
|
+
|
91
|
+
user.not_nil_name = nil
|
92
|
+
assert { user.not_nil_name == "" }
|
93
|
+
|
94
|
+
user.not_nil_name = "joker"
|
95
|
+
assert { user.not_nil_name == "joker" }
|
96
|
+
|
97
|
+
user.role = "super_user"
|
98
|
+
assert { user.role == :super_user }
|
99
|
+
|
100
|
+
user.age = 5
|
101
|
+
assert { user.age == 5 }
|
102
|
+
|
103
|
+
user.age = "unknown"
|
104
|
+
assert { user.age == 0 }
|
105
|
+
|
106
|
+
user.birthday = "2012-10-01"
|
107
|
+
assert { user.birthday == Date.new(2012, 10, 1) }
|
108
|
+
|
109
|
+
user.birthday = Date.new(2012, 10, 2)
|
110
|
+
assert { user.birthday == Date.new(2012, 10, 2) }
|
111
|
+
|
112
|
+
user.birthday_time = "2012-10-01 10:00:00"
|
113
|
+
assert { user.birthday_time == Time.new(2012, 10, 1, 10) }
|
114
|
+
|
115
|
+
user.birthday_datetime = "2012-10-01 10:00:00"
|
116
|
+
assert { user.birthday_datetime.is_a?(DateTime) }
|
117
|
+
assert { user.birthday_datetime == Time.new(2012, 10, 1, 10) }
|
118
|
+
|
119
|
+
user.birthday_datetime = Time.new(2012, 10, 2, 2)
|
120
|
+
assert { user.birthday_datetime.is_a?(DateTime) }
|
121
|
+
assert { user.birthday_datetime == Time.new(2012, 10, 2, 2) }
|
122
|
+
|
123
|
+
user.bmi = "20.1"
|
124
|
+
assert { user.bmi > 20.09 && user.bmi < 20.11 }
|
125
|
+
|
126
|
+
user.bmi = "20.1"
|
127
|
+
assert { user.bmi == "20.1".to_r }
|
128
|
+
|
129
|
+
user.property = 10000
|
130
|
+
assert { user.property == Money.new(10000) }
|
131
|
+
|
132
|
+
user.debt = 10000
|
133
|
+
assert { user.debt == BigDecimal.new("10000") }
|
134
|
+
user.debt = Rational(10000, 1)
|
135
|
+
assert { user.debt == BigDecimal.new("10000") }
|
136
|
+
user.debt = 10000.11
|
137
|
+
assert { user.debt == BigDecimal.new("10000.11") }
|
138
|
+
user.debt = "10000.12"
|
139
|
+
assert { user.debt == BigDecimal.new("10000.12") }
|
140
|
+
|
141
|
+
user.debt_with_precision = 1.3333333333
|
142
|
+
assert { user.debt_with_precision == BigDecimal.new("1.33") }
|
143
|
+
|
144
|
+
user.debt_with_fig = Rational("1000.11")
|
145
|
+
assert { user.debt_with_fig == BigDecimal.new("1000.1") }
|
146
|
+
|
147
|
+
user.adult = "true"
|
148
|
+
assert { user.adult == true }
|
149
|
+
|
150
|
+
user.adult = "other"
|
151
|
+
assert { user.adult == true }
|
152
|
+
|
153
|
+
user.adult = "false"
|
154
|
+
assert { user.adult == false }
|
155
|
+
|
156
|
+
user.adult = 1
|
157
|
+
assert { user.adult == true }
|
158
|
+
|
159
|
+
user.adult = 0
|
160
|
+
assert { user.adult == false }
|
161
|
+
|
162
|
+
user.admin = "yes"
|
163
|
+
assert { user.admin == true }
|
164
|
+
|
165
|
+
user.admin = false
|
166
|
+
assert { user.admin == false }
|
167
|
+
|
168
|
+
user.active = "tRUe"
|
169
|
+
assert { user.active == true }
|
170
|
+
|
171
|
+
assert { user.manager == false }
|
172
|
+
|
173
|
+
user.manager = "true"
|
174
|
+
assert { user.manager == true }
|
175
|
+
|
176
|
+
user.default_name = "Changed"
|
177
|
+
user.reset_default_name!
|
178
|
+
assert { user.default_name == "Foo" }
|
179
|
+
|
180
|
+
assert { user.raw_value == 1 }
|
181
|
+
user.raw_value = "raw"
|
182
|
+
assert { user.raw_value == "raw" }
|
183
|
+
|
184
|
+
user.raw_value2 = "raw_value2"
|
185
|
+
assert { user.raw_value2 == "raw_value2" }
|
186
|
+
|
187
|
+
assert { user.raw_value3 == "raw_value3" }
|
188
|
+
user.raw_value3 = 3.0
|
189
|
+
assert { user.raw_value3 > 2.9 }
|
190
|
+
|
191
|
+
assert { user.skills == [] }
|
192
|
+
user.skills = "ruby"
|
193
|
+
assert { user.skills == ["ruby"] }
|
194
|
+
user.skills = 1
|
195
|
+
assert { user.skills == ["1"] }
|
196
|
+
user.skills = ["ruby", "js", 1, [2]]
|
197
|
+
assert { user.skills == ["ruby", "js", "1", "[2]"] }
|
198
|
+
|
199
|
+
assert { user.friend_ids.nil? }
|
200
|
+
user.friend_ids = 1
|
201
|
+
assert { user.friend_ids == [1] }
|
202
|
+
user.friend_ids = ["1", 2, 3]
|
203
|
+
assert { user.friend_ids == [1, 2, 3] }
|
204
|
+
|
205
|
+
user.nested_array = 1
|
206
|
+
assert { user.nested_array == [[1]] }
|
207
|
+
user.nested_array = [2]
|
208
|
+
assert { user.nested_array == [[2]] }
|
209
|
+
user.nested_array = [[2, "3"]]
|
210
|
+
assert { user.nested_array == [[2, 3]] }
|
211
|
+
|
212
|
+
user2 = User.new(name: 20)
|
213
|
+
assert { user2.name == "20" }
|
214
|
+
assert { user2.default_name == "Foo" }
|
215
|
+
assert { user2.conditional_default == "Hello 20" }
|
216
|
+
user2.conditional_default = "hoge"
|
217
|
+
assert { user2.conditional_default == "hoge" }
|
218
|
+
user2.reset_conditional_default!
|
219
|
+
assert { user2.conditional_default == "Hello 20" }
|
220
|
+
|
221
|
+
user3 = User.new(default_name: "kakyoin")
|
222
|
+
assert { user3.name.nil? }
|
223
|
+
assert { user3.default_name == "kakyoin" }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'coveralls'
|
3
|
+
Coveralls.wear!
|
4
|
+
|
5
|
+
require 'attr_typecastable'
|
6
|
+
|
7
|
+
require 'rspec-power_assert'
|
8
|
+
RSpec::PowerAssert.example_assertion_alias :assert
|
9
|
+
RSpec::PowerAssert.example_group_assertion_alias :assert
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attr_typecastable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- joker1007
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coveralls
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: attr_accessor with typecast feature.
|
84
|
+
email:
|
85
|
+
- kakyoin.hierophant@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- attr_typecastable.gemspec
|
98
|
+
- lib/attr_typecastable.rb
|
99
|
+
- lib/attr_typecastable/initialize_hook.rb
|
100
|
+
- lib/attr_typecastable/reflection.rb
|
101
|
+
- lib/attr_typecastable/types.rb
|
102
|
+
- lib/attr_typecastable/types/array.rb
|
103
|
+
- lib/attr_typecastable/types/base.rb
|
104
|
+
- lib/attr_typecastable/types/big_decimal.rb
|
105
|
+
- lib/attr_typecastable/types/boolean.rb
|
106
|
+
- lib/attr_typecastable/types/date.rb
|
107
|
+
- lib/attr_typecastable/types/date_time.rb
|
108
|
+
- lib/attr_typecastable/types/exception.rb
|
109
|
+
- lib/attr_typecastable/types/float.rb
|
110
|
+
- lib/attr_typecastable/types/integer.rb
|
111
|
+
- lib/attr_typecastable/types/object.rb
|
112
|
+
- lib/attr_typecastable/types/rational.rb
|
113
|
+
- lib/attr_typecastable/types/string.rb
|
114
|
+
- lib/attr_typecastable/types/symbol.rb
|
115
|
+
- lib/attr_typecastable/types/time.rb
|
116
|
+
- lib/attr_typecastable/version.rb
|
117
|
+
- spec/attr_typecastable_spec.rb
|
118
|
+
- spec/performance_check.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
homepage: https://github.com/joker1007/attr_typecastable
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.4.1
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: attr_accessor with typecast feature.
|
144
|
+
test_files:
|
145
|
+
- spec/attr_typecastable_spec.rb
|
146
|
+
- spec/performance_check.rb
|
147
|
+
- spec/spec_helper.rb
|