multitype 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 31fd13aa6edb45c77dff6a27f1f0e86205b26058
4
+ data.tar.gz: 34651ec1ffb2ae2067988b8bbd3d39a17ba0e16f
5
+ SHA512:
6
+ metadata.gz: 6453bac963334d070f745ae156e3030c30c4a0a9e03192b14d06fe17c5d5632516bf9268d7f579526af7851ba9961c704dec0c9719ed50516a7b061cdd9015d2
7
+ data.tar.gz: c48478da8607993dfd77103094fff64eca7c8fc2df102e5ad55c671e85352bde606d10908cbd9ee31a199e4f1c938c12e5de0c1eebe6d3d0cd4f403336061dec
data/.gitignore ADDED
@@ -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/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format documentation
3
+ --profile
4
+ --backtrace
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in multitype.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kelly Becker
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,32 @@
1
+ # Multitype
2
+
3
+ Allows for variable methods and attributes on models and classes to help differentiate different types of calculations for similar data structures seamlessly.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'multitype'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install multitype
18
+
19
+ ## Usage
20
+
21
+ Please refer to the specs and the "FakeModel" for basic usage.
22
+
23
+ - [FakeModel](http://github.com/JIFFinc/multitype/blob/master/spec/support/fake_model.rb)
24
+ - [Spec](http://github.com/JIFFinc/multitype/blob/master/spec/tests/multitype_spec.rb)
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it
29
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin feature/my-new-feature`)
32
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ module Multitype
2
+ VERSION = "0.0.1"
3
+ end
data/lib/multitype.rb ADDED
@@ -0,0 +1,241 @@
1
+ require "active_support/core_ext/class/attribute"
2
+ require "multitype/version"
3
+ require "awesome_print"
4
+
5
+ module Multitype
6
+
7
+ class NoTypeError < NoMethodError; end
8
+ class NoTypeCatError < NoTypeError; end
9
+ class NoTypeSetError < NoTypeError; end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
15
+ # Return the multitype data from the cache
16
+ [:comparators, :typesets].each do |method|
17
+ __send__(:define_method, "get_multitype_#{method}") do |type|
18
+ __get_multitype_cache__(type, method)
19
+ end
20
+ end
21
+
22
+ # Forward this method to the class method
23
+ def __get_multitype_cache__(*args)
24
+ self.class.__get_multitype_cache__(*args)
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ def type_comparator(type, *comparators)
30
+ __send__(:attr_accessor, *comparators)
31
+ __create_multitype_datastore__
32
+
33
+ # Get the type name if a typeset is passed as an object
34
+ type = type.type if type.is_a?(Object) && type.respond_to?(:__multitype_typeset__)
35
+
36
+ # Append to the list comparator columns
37
+ __update_multitype_cache__(type, :comparators, comparators)
38
+ end
39
+
40
+ def type_alias(type, *aliases)
41
+ __create_multitype_datastore__
42
+
43
+ # Get the type name if a typeset is passed as an object
44
+ type = type.type if type.is_a?(Object) && type.respond_to?(:__multitype_typeset__)
45
+ raise NoTypeError, type.to_s unless __get_multitype_cache__(type)
46
+
47
+ aliases.each do |_alias|
48
+ alias_method _alias, type
49
+ end
50
+ end
51
+
52
+ def type_accessor(type, *columns)
53
+ __create_multitype_datastore__
54
+
55
+ # Add accessors to the typeset that is passed or any typesets matching type
56
+ if type.is_a?(Object) && type.respond_to?(:__multitype_typeset__)
57
+ type.class.__send__(:attr_accessor, *columns)
58
+ else
59
+ raise NoTypeError, type.to_s unless __get_multitype_cache__(type)
60
+ raise NoTypeSetError, type.to_s unless __get_multitype_cache__(type, :typesets)
61
+ __get_multitype_cache__(type, :typesets).each do |name, typeset|
62
+ typeset.class.attr_accessor(*columns)
63
+ end
64
+ end
65
+ end
66
+
67
+ def deftype(type, name, args = {}, &block)
68
+ __create_multitype_datastore__
69
+ type = type.to_sym
70
+ name = name.to_sym
71
+
72
+ # Merge in name, type and model for accessable access
73
+ args.merge!(type: type, name: name, model: self)
74
+
75
+ # Get the list of accessable keys we need
76
+ accessable = args.keys.map(&:to_sym)
77
+
78
+ # Kickstart the type comparators
79
+ type_comparator(type)
80
+
81
+ # Get the name of the class to extend if it exists
82
+ type_class = if args[:class]
83
+ klass = if args[:class].is_a?(String)
84
+ get_const(args[:class])
85
+ else
86
+ args[:class]
87
+ end
88
+
89
+ klass.is_a?(Object) ? klass : Object
90
+ else
91
+ Object
92
+ end
93
+
94
+ # Define the class and mark as a typeset
95
+ object = Class.new(type_class)
96
+ object.class_attribute :__multitype_typeset__
97
+ object.__multitype_typeset__ = true
98
+
99
+ # Execute the passed block as if it was class level code
100
+ object.class_exec(&block) if block_given?
101
+
102
+ # Instantiate the object
103
+ object = object.new
104
+
105
+ # Make the variables passed accessable
106
+ type_accessor(object, *accessable)
107
+
108
+ # Set the instance attributes that were passed in
109
+ args.each { |key, val| object.__send__("#{key}=", val) }
110
+
111
+ # Add the type to the multitype cache
112
+ __update_multitype_cache__(type, :typesets, {name => object})
113
+
114
+ # Create the accessor method if it does not exist
115
+ unless self.respond_to?(type)
116
+ self.__send__(:define_method, type) do
117
+ use_typeset = nil # Object to return
118
+
119
+ # Loop through the types to find the one to use
120
+ __get_multitype_cache__(type, :typesets).each do |name, typeset|
121
+ match = true # Did we find a match
122
+
123
+ # Loop through the type comparators to find a match
124
+ __get_multitype_cache__(type, :comparators).each do |comparator|
125
+
126
+ # Check the comparator for a match
127
+ match = if typeset.respond_to?(comparator) && self.respond_to?(comparator)
128
+ typeset.__send__(comparator) == self.__send__(comparator)
129
+ else
130
+ false
131
+ end
132
+
133
+ # If we fail at least one
134
+ # match then break the loop
135
+ break unless match
136
+ end
137
+
138
+ # If we have a match,
139
+ # stop looking for one
140
+ if match
141
+ use_typeset = typeset
142
+ break
143
+ end
144
+ end
145
+
146
+ # Return the typeset
147
+ use_typeset
148
+ end
149
+ end
150
+
151
+ object
152
+ end
153
+
154
+ def __get_multitype_cache__(type = nil, cat = nil, klass = self.name.to_sym, depth = 0)
155
+ type = type.to_sym if type
156
+ cat = cat.to_sym if cat
157
+
158
+ # If the klass is not cached just return nil
159
+ return nil unless __multitype_cache__[klass]
160
+
161
+ # Return the level of data requested
162
+ result = if type.nil? && cat.nil?
163
+ __multitype_cache__[klass]
164
+ elsif cat.nil? && __multitype_cache__[klass]
165
+ __multitype_cache__[klass][type]
166
+ elsif __multitype_cache__[klass] && __multitype_cache__[klass][type]
167
+ __multitype_cache__[klass][type][cat]
168
+ else
169
+ nil
170
+ end
171
+
172
+ # Don't loop through ancestors on lower objects
173
+ return result if depth > 0
174
+
175
+ # Iterate over ancestors to use their typesets
176
+ ancestors.inject(result) do |out, ancestor|
177
+ ancestor = ancestor.name.to_sym
178
+
179
+ # Skip if ancestor is self or Multitype
180
+ if ancestor == klass || ancestor == 'Multitype'
181
+ out
182
+ else
183
+
184
+ # Get data lower down the ancestry tree
185
+ merge_in = __get_multitype_cache__(type, cat, ancestor, depth + 1)
186
+
187
+ # Merge in data as required
188
+ if out.nil? && ! merge_in.nil?
189
+ merge_in
190
+ else
191
+ if merge_in.is_a?(Array)
192
+ out.concat(merge_in)
193
+ elsif merge_in.is_a?(Hash)
194
+ out.merge(merge_in)
195
+ else
196
+ out
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ private
204
+
205
+ def __update_multitype_cache__(type, cat, data)
206
+ klass = self.name.to_sym
207
+ type = type.to_sym
208
+ cat = cat.to_sym
209
+
210
+ # Create the hashes if they don't exist
211
+ __multitype_cache__[klass] ||= {}
212
+ __multitype_cache__[klass][type] ||= {}
213
+ __multitype_cache__[klass][type][cat] ||= data
214
+
215
+ # Merge in the data for hashes
216
+ if data.is_a?(Hash)
217
+ data = __multitype_cache__[klass][type][cat].merge(data)
218
+ __multitype_cache__[klass][type][cat] = data
219
+ end
220
+
221
+ # Concatanate the data for arrays
222
+ if data.is_a?(Array)
223
+ data = __multitype_cache__[klass][type][cat].concat(data).uniq
224
+ __multitype_cache__[klass][type][cat] = data
225
+ end
226
+
227
+ __multitype_cache__[klass]
228
+ end
229
+
230
+ def __create_multitype_datastore__
231
+ if ! self.respond_to?(:__multitype_cache__)
232
+ class_attribute :__multitype_cache__
233
+ self.__send__(:__multitype_cache__=, {})
234
+ true
235
+ else
236
+ false
237
+ end
238
+ end
239
+ end
240
+ end
241
+
data/multitype.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'multitype/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "multitype"
8
+ spec.version = Multitype::VERSION
9
+ spec.authors = ["Kelly Becker"]
10
+ spec.email = ["kellylsbkr@gmail.com"]
11
+ spec.description = %q{Allows for dynamic methods and data dependent on the data on a classes instance attributes}
12
+ spec.summary = %q{Add dynamic methods for ruby classes}
13
+ spec.homepage = "http://github.com/JIFFinc/multitype"
14
+
15
+ # Determine the proper license for this code
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "awesome_print"
27
+ spec.add_dependency "activesupport", ">= 3.2.13"
28
+ end
@@ -0,0 +1,20 @@
1
+ require 'multitype'
2
+ Dir[File.join(File.dirname(__FILE__), 'support', '*.rb')].each { |f| require f }
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # Require this file using `require "spec_helper"` to ensure that it is only
7
+ # loaded once.
8
+ #
9
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+ end
@@ -0,0 +1,5 @@
1
+ class ATypeSet
2
+ def run
3
+ :works
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ class BTypeSet
2
+ end
@@ -0,0 +1,31 @@
1
+ class FakeModel
2
+ include Multitype
3
+
4
+ def initialize(compare = nil)
5
+ @compare = compare
6
+ end
7
+
8
+ type_comparator :math, :compare
9
+
10
+ deftype :math, 'Addition', compare: :addition do
11
+ def run(num1, num2)
12
+ num1 + num2
13
+ end
14
+ end
15
+
16
+ deftype :math, 'Multiplication', compare: :multiplication do
17
+ def run(num1, num2)
18
+ num1 * num2
19
+ end
20
+
21
+ def message
22
+ "Method Exists"
23
+ end
24
+ end
25
+
26
+ deftype :general, 'APrefined', class: ATypeSet
27
+ deftype :general, 'BPrefined', class: BTypeSet
28
+
29
+ deftype :override, 'Prefined', class: BTypeSet
30
+ deftype :override, 'Prefined', class: ATypeSet
31
+ end
@@ -0,0 +1,10 @@
1
+ class FakeModel2 < FakeModel
2
+
3
+ deftype :pizza, 'Pizza' do
4
+ def run
5
+ :pizza
6
+ end
7
+ end
8
+
9
+ type_alias :pizza, :sauce
10
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Test the multitype with FakeModel" do
4
+ it "Should evaluate 5 + 2 using a type" do
5
+ result = FakeModel.new(:addition).math.run(5, 2)
6
+ result.should == 7
7
+ end
8
+
9
+ it "Should evaluate 5 * 2 using a type" do
10
+ result = FakeModel.new(:multiplication).math.run(5, 2)
11
+ result.should == 10
12
+ end
13
+
14
+ it "Should not find the method `message`" do
15
+ expect do
16
+ FakeModel.new(:addition).math.message()
17
+ end.to raise_error
18
+ end
19
+
20
+ it "Should find the method `message`" do
21
+ result = FakeModel.new(:multiplication).math.message()
22
+ result.should == "Method Exists"
23
+ end
24
+
25
+ it "Should allow use of a predefined class inside the type" do
26
+ result = FakeModel.new.general.run()
27
+ result.should == :works
28
+ end
29
+
30
+ it "Should run the first defined typeset if there is no comparator" do
31
+ result = FakeModel.new.general.run()
32
+ result.should == :works
33
+ end
34
+
35
+ it "Should override typeset if you redeclare with the same name" do
36
+ result = FakeModel.new.override.run()
37
+ result.should == :works
38
+ end
39
+ end
40
+
41
+ describe "Test the multitype with FakeModel2 (inherits FakeModel)" do
42
+ it "Should evaluate 5 + 2 using a type (inherited from FakeModel)" do
43
+ result = FakeModel2.new(:addition).math.run(5, 2)
44
+ result.should == 7
45
+ end
46
+
47
+ it "Should evaluate 5 * 2 using a type (inherited from FakeModel)" do
48
+ result = FakeModel2.new(:multiplication).math.run(5, 2)
49
+ result.should == 10
50
+ end
51
+
52
+ it "Should define a new type `pizza` on FakeModel2" do
53
+ result = FakeModel2.new.pizza.run()
54
+ result.should == :pizza
55
+ end
56
+
57
+ it "Should use alias defined for `pizza`" do
58
+ result = FakeModel2.new.sauce.run()
59
+ result.should == :pizza
60
+ end
61
+
62
+ it "Pizza should not exist on FakeModel" do
63
+ expect do
64
+ FakeModel.new.pizza.run()
65
+ end.to raise_error
66
+ end
67
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multitype
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kelly Becker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-07 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: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: awesome_print
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: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 3.2.13
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 3.2.13
83
+ description: Allows for dynamic methods and data dependent on the data on a classes
84
+ instance attributes
85
+ email:
86
+ - kellylsbkr@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - .rspec
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/multitype.rb
98
+ - lib/multitype/version.rb
99
+ - multitype.gemspec
100
+ - spec/spec_helper.rb
101
+ - spec/support/a_type_set.rb
102
+ - spec/support/b_type_set.rb
103
+ - spec/support/fake_model.rb
104
+ - spec/support/fake_model_2.rb
105
+ - spec/tests/multitype_spec.rb
106
+ homepage: http://github.com/JIFFinc/multitype
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.0.3
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Add dynamic methods for ruby classes
130
+ test_files:
131
+ - spec/spec_helper.rb
132
+ - spec/support/a_type_set.rb
133
+ - spec/support/b_type_set.rb
134
+ - spec/support/fake_model.rb
135
+ - spec/support/fake_model_2.rb
136
+ - spec/tests/multitype_spec.rb
137
+ has_rdoc: