flex_coerce 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: 24baca495e6e4c9a7f8d1da2f74de3166751db50
4
+ data.tar.gz: 7707c8373d1dd02a1d2f4a3e387752c3036e16fc
5
+ SHA512:
6
+ metadata.gz: 6f8bf58b33f181f6060d97d6d3f6cb802c7b1f96cf23cd6b59ce30214b216edc5069f23c15b830f7b8b830baeb9f2ddd2dca51d66a246f0bb14d88640748337e
7
+ data.tar.gz: c987e94f52aeadd3b2dbf2da9856ff41accbacfc65bf71204467e08d67d490f27abf9d56eec4255a422496c54f6f12530732f957cd045648a0869bbf0dd3be81
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flex_coerce.gemspec
4
+ gemspec
5
+
6
+ ruby '2.0.0'
7
+
8
+ group :development do
9
+ gem 'rspec', '>= 2.0'
10
+ gem 'rspec-given', '>= 2.0.0'
11
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ilya Vorontsov
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,72 @@
1
+ # FlexCoerce
2
+
3
+ FlexCoerce - is a gem which allow you create operator-dependent coercion logic. It's useful when your type should be treated in a different way for different binary operations (including arithmetic operators, bitwise operators and comparison operators except equality checks: `==`, `===`).
4
+
5
+ ## Installation
6
+
7
+ Attention: this gem works only with Ruby 2.0 because it uses Module#prepend which has no analogs in Ruby 1.9. If you have an idea how to realize it on 1.9 without too much mess - your pull-requests are welcome.
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'flex_coerce'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install flex_coerce
20
+
21
+ ## Usage
22
+
23
+ I'll show you an example of (very simplistic) class Unit for physical units. We can multiply numbers with units but can't add them.
24
+
25
+ ```
26
+ require 'flex_coerce'
27
+ class Unit
28
+ attr_reader :num, :unit
29
+ def initialize(num, unit)
30
+ @num, @unit = num, unit
31
+ end
32
+ def +(other)
33
+ raise TypeError, 'Can\'t add Unit to a non-Unit' unless other.is_a? Unit
34
+ raise ArgumentError, 'Can\'t sum units of different types' unless other.unit == unit
35
+ Unit.new(num + other.num, unit)
36
+ end
37
+ def *(other)
38
+ return self * Unit.new(other, '1') if other.is_a? Numeric
39
+ Unit.new(num * other.num, "#{unit}*#{other.unit}")
40
+ end
41
+ def coerce(other, meth = nil)
42
+ raise TypeError, 'Coercion error: can\'t coerce non-numeric class to a Unit' unless other.is_a? Numeric
43
+ case meth.to_sym
44
+ when :*
45
+ [Unit.new(other,'1'), self]
46
+ when :+
47
+ raise TypeError 'Coercion error: can\'t compare Unit to a non-unit anything'
48
+ [other, self]
49
+ else
50
+ raise TypeError, 'Unsupported operation'
51
+ end
52
+ end
53
+
54
+ prepend FlexCoerce
55
+ end
56
+
57
+ 5 * Unit.new(3,'cm') # ==> Unit(15,'1*cm')
58
+ 5 + Unit.new(3,'cm') # ==> raises an Error
59
+ ```
60
+
61
+ You define your own `#coerce` with two arguments: standard argument `other` and new argument `meth` which represents the binary operations caused coercion. All the magic is in `prepend FlexCoerce` call.
62
+ It decorates your `#coerce` method with another one, which follow ruby conventions about coerce method. This is done by creating an instance of `CoerceableWrapper` class which takes a reference to left-side object. This intermediate class defines its own methods for binary operators such that they play well with `#coerce` method of right-side object class. I should mention that this method works good for missing, optional or required second argument of `#coerce`, you can find examples in specs.
63
+
64
+ Possibly this example is too simplistic and can be made in another and more consistent way. But this is a proof of concept that coerce can be made method-specific without touching base ruby classes. You're welcome with more interesting examples and more powerful and clear design.
65
+
66
+ ## Contributing
67
+
68
+ 1. Fork it
69
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
71
+ 4. Push to the branch (`git push origin my-new-feature`)
72
+ 5. Create new Pull Request
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flex_coerce/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "flex_coerce"
8
+ spec.version = FlexCoerce::VERSION
9
+ spec.authors = ["Ilya Vorontsov"]
10
+ spec.email = ["prijutme4ty@gmail.com"]
11
+ spec.description = %q{FlexCoerce - is a gem which allow you create operator-dependent coercion logic. It's useful when your type should be treated in a different way for different binary operations (including arithmetic operators, bitwise operators and comparison operators except equality checks: `==`, `===`).}
12
+ spec.summary = %q{FlexCoerce - is a gem which allow you create operator-dependent coercion logic. It's useful when your type should be treated in a different way for different binary operations (including arithmetic operators, bitwise operators and comparison operators except equality checks: `==`, `===`).}
13
+ spec.homepage = "https://github.com/prijutme4ty/flex_coerce"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
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_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency 'rspec', '>= 2.0'
24
+ spec.add_development_dependency 'rspec-given', '>= 2.0.0'
25
+
26
+ spec.required_ruby_version = '2.0.0'
27
+ end
@@ -0,0 +1,6 @@
1
+ require "flex_coerce/version"
2
+ require 'flex_coerce/flex_coerce'
3
+
4
+ module FlexCoerce
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,37 @@
1
+ module FlexCoerce
2
+ def self.prepend_features(base)
3
+ base.instance_eval{
4
+ @__original_coerce_arity ||= base.instance_method(:coerce).arity
5
+ }
6
+ super
7
+ end
8
+ def coerce(other, meth = nil)
9
+ if meth
10
+ case self.class.class_eval{ @__original_coerce_arity }
11
+ when 1
12
+ super(other.obj)
13
+ when 2, -2
14
+ # coerce(other, meth) or coerce(other, meth = nil)
15
+ super(other.obj, meth)
16
+ else
17
+ raise 'Too many arguments for coerce'
18
+ end
19
+ else
20
+ [CoerceableWrapper.new(other), self]
21
+ end
22
+ end
23
+ end
24
+
25
+ # This class is a wrapper for object which redefines methods leading to coercion
26
+ class CoerceableWrapper
27
+ attr_reader :obj
28
+ def initialize(obj)
29
+ @obj = obj
30
+ end
31
+ [:+, :-, :*, :/, :%, :div, :divmod, :fdiv, :**, :&, :|, :^, :>, :>=, :<, :<=, :<=>].each do |op|
32
+ define_method(op) do |other|
33
+ self_coerced, other_coerced = other.coerce(self, op)
34
+ self_coerced.send(op, other_coerced)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module FlexCoerce
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,123 @@
1
+ $:.unshift '../lib/'
2
+ require 'flex_coerce'
3
+ require 'rspec/given'
4
+
5
+ class UnitNoOptionalArgument
6
+ attr_reader :num
7
+ def initialize(num) @num = num; end
8
+ def coerce(other) [other, self.num]; end
9
+ end
10
+
11
+ class UnitBothRequired
12
+ attr_reader :num
13
+ def initialize(num) @num = num; end
14
+ def coerce(other, meth) [other, self.num]; end
15
+ end
16
+
17
+ class UnitWeakCheck
18
+ attr_reader :num
19
+ def initialize(num) @num = num; end
20
+ def coerce(other, meth = nil) [other, self.num]; end
21
+ end
22
+
23
+ class UnitStrongCheck
24
+ attr_reader :num
25
+ def initialize(num) @num = num; end
26
+
27
+ def coerce(other, meth = nil)
28
+ raise TypeError 'Coercing error' unless meth
29
+ case meth.to_sym
30
+ when :*, :/
31
+ [other, self.num]
32
+ when :+, :-
33
+ raise TypeError, 'Coercing error'
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+
40
+ describe FlexCoerce do
41
+ context 'with #coerce having the only required and no optional arguments' do
42
+ Given(:unit_class) { UnitNoOptionalArgument }
43
+ Given(:number){ 3 }
44
+ Given(:unit) { unit_class.new(2) }
45
+ context 'before prepending FlexCoerce' do
46
+ When(:plus_operation) { number + unit }
47
+ Then{plus_operation.should == 5}
48
+ When(:mul_operation) { number * unit }
49
+ Then{mul_operation.should == 6}
50
+ end
51
+ context 'after prepending FlexCoerce' do
52
+ When{ unit_class.send :prepend, FlexCoerce }
53
+
54
+ When(:plus_operation) { number + unit }
55
+ Then{plus_operation.should == 5}
56
+ When(:mul_operation) { number * unit }
57
+ Then{mul_operation.should == 6}
58
+ end
59
+ end
60
+
61
+ context 'with #coerce having the only required and no optional arguments' do
62
+ context 'with coerce not checking method' do
63
+ Given(:unit_class) { UnitWeakCheck }
64
+ Given(:number){ 3 }
65
+ Given(:unit) { unit_class.new(2) }
66
+ context 'before prepending FlexCoerce' do
67
+ When(:plus_operation) { number + unit }
68
+ Then{plus_operation.should == 5}
69
+ When(:mul_operation) { number * unit }
70
+ Then{mul_operation.should == 6}
71
+ end
72
+ context 'after prepending FlexCoerce' do
73
+ When { unit_class.send :prepend, FlexCoerce }
74
+
75
+ When(:plus_operation) { number + unit }
76
+ Then{plus_operation.should == 5}
77
+ When(:mul_operation) { number * unit }
78
+ Then{mul_operation.should == 6}
79
+ end
80
+ end
81
+
82
+ context 'with coerce checking method' do
83
+ Given(:unit_class) { UnitStrongCheck }
84
+ Given(:number){ 3 }
85
+ Given(:unit) { unit_class.new(2) }
86
+ context 'before prepending FlexCoerce' do
87
+ When(:plus_operation) { number + unit }
88
+ Then{plus_operation.should have_failed}
89
+ When(:mul_operation) { number * unit }
90
+ Then{mul_operation.should have_failed}
91
+ end
92
+ context 'after prepending FlexCoerce' do
93
+ When { unit_class.send :prepend, FlexCoerce }
94
+
95
+ When(:plus_operation) { number + unit }
96
+ Then{plus_operation.should have_failed}
97
+ When(:mul_operation) { number * unit }
98
+ Then{mul_operation.should == 6}
99
+ end
100
+ end
101
+ end
102
+
103
+ context 'with #coerce having two required arguments' do
104
+ Given(:unit_class) { UnitBothRequired }
105
+ Given(:number){ 3 }
106
+ Given(:unit) { unit_class.new(2) }
107
+ context 'before prepending FlexCoerce' do
108
+ When(:plus_operation) { number + unit }
109
+ Then{plus_operation.should have_failed}
110
+ When(:mul_operation) { number * unit }
111
+ Then{mul_operation.should have_failed}
112
+ end
113
+ context 'after prepending FlexCoerce' do
114
+ When { unit_class.send :prepend, FlexCoerce }
115
+
116
+ When(:plus_operation) { number + unit }
117
+ Then{plus_operation.should == 5}
118
+ When(:mul_operation) { number * unit }
119
+ Then{mul_operation.should == 6}
120
+ end
121
+ end
122
+
123
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flex_coerce
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ilya Vorontsov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-given
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.0
69
+ description: 'FlexCoerce - is a gem which allow you create operator-dependent coercion
70
+ logic. It''s useful when your type should be treated in a different way for different
71
+ binary operations (including arithmetic operators, bitwise operators and comparison
72
+ operators except equality checks: `==`, `===`).'
73
+ email:
74
+ - prijutme4ty@gmail.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - .gitignore
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - flex_coerce.gemspec
85
+ - lib/flex_coerce.rb
86
+ - lib/flex_coerce/flex_coerce.rb
87
+ - lib/flex_coerce/version.rb
88
+ - spec/flex_coerce_spec.rb
89
+ homepage: https://github.com/prijutme4ty/flex_coerce
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '='
100
+ - !ruby/object:Gem::Version
101
+ version: 2.0.0
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.0.2
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: 'FlexCoerce - is a gem which allow you create operator-dependent coercion
113
+ logic. It''s useful when your type should be treated in a different way for different
114
+ binary operations (including arithmetic operators, bitwise operators and comparison
115
+ operators except equality checks: `==`, `===`).'
116
+ test_files:
117
+ - spec/flex_coerce_spec.rb