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.
@@ -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