attr_typecastable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ -r power_assert
2
+ --format documentation
3
+ --color
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 2.1.3
5
+ - 2.2
6
+ notifications:
7
+ email:
8
+ on_success: change
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'
@@ -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.
@@ -0,0 +1,137 @@
1
+ # attr\_typecastable
2
+ [![Build Status](https://travis-ci.org/joker1007/attr_typecastable.svg?branch=master)](https://travis-ci.org/joker1007/attr_typecastable)
3
+ [![Coverage Status](https://coveralls.io/repos/joker1007/attr_typecastable/badge.png)](https://coveralls.io/r/joker1007/attr_typecastable)
4
+ [![Code Climate](https://codeclimate.com/github/joker1007/attr_typecastable/badges/gpa.svg)](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
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -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,9 @@
1
+ module AttrTypecastable
2
+ module Types
3
+ class TypeCasterNotFound < StandardError
4
+ end
5
+
6
+ class CastError < StandardError
7
+ end
8
+ end
9
+ 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,14 @@
1
+ require 'attr_typecastable/types/exception'
2
+ require 'attr_typecastable/types/base'
3
+
4
+ module AttrTypecastable
5
+ module Types
6
+ class Object < Base
7
+ private
8
+
9
+ def do_typecast(value)
10
+ value
11
+ end
12
+ end
13
+ end
14
+ 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,3 @@
1
+ module AttrTypecastable
2
+ VERSION = "0.0.1"
3
+ 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
@@ -0,0 +1,14 @@
1
+ require 'benchmark/ips'
2
+ require 'attr_typecastable'
3
+
4
+ class User
5
+ include AttrTypecastable
6
+
7
+ typed_attr_accessor :name, String
8
+ end
9
+
10
+ user = User.new
11
+
12
+ Benchmark.ips do |x|
13
+ x.report("writer") { user.name = 10 }
14
+ end
@@ -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