choose_class 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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Joel Plane
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,91 @@
1
+ # choose_class
2
+
3
+ Facilitates choosing a class based on the value that would be passed to that class.
4
+ Useful for implementing the factory method pattern.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'choose_class'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install choose_class
19
+
20
+ ## Example Usage
21
+
22
+ Include ChooseClass::Base into your base class. It will gain a choose_class
23
+ method. Call choose_class and pass it a value by which to decide which class
24
+ to return.
25
+
26
+ Say we're implementing an object serializer of some sort. We may have a base
27
+ class that implments some common functionalty, and then several subclasses
28
+ that specialise the serialization for specific types.
29
+
30
+ We want to determine which class to use based on the object we are to
31
+ serialize.
32
+
33
+ ```ruby
34
+ module Serializers
35
+ class Base
36
+ include ChooseClass::Base
37
+ end
38
+
39
+ # Specialisation class for Fixnum values
40
+ class Fixnum < Base
41
+ end
42
+ end
43
+ ```
44
+
45
+ elsewhere:
46
+
47
+ ```ruby
48
+ Serializers.choose_class(1) # => Serializers::Fixnum
49
+ ```
50
+
51
+ The class chosen was Serializers::Fixnum because it is a class in the same
52
+ namespace as the base class, and it has the same name as the class of the value
53
+ passed in. (These conventions are configurable in a limited way. See spec/example)
54
+
55
+ ```ruby
56
+ Serializers.choose_class("some string") # => Serializers::Base
57
+ ```
58
+
59
+ The class chosen was Serializers::Base because there was no Serializers::String
60
+ class to choose instead.
61
+
62
+ To complete the serializer example, a final implementation might look something
63
+ like this:
64
+
65
+ ```ruby
66
+ module Serializers
67
+ class Base
68
+ include ChooseClass::Base
69
+
70
+ def self.serialize value
71
+ choose_class(value).new(value).serialize
72
+ end
73
+
74
+ def serialize
75
+ # general object serialization implementation
76
+ end
77
+ end
78
+
79
+ # Specialisation class for Fixnum values
80
+ class Fixnum < Base
81
+ def serialize
82
+ # fixnum-specific serialization implementation
83
+ end
84
+ end
85
+ end
86
+
87
+ Serializers.serialize(1) # => output of Serializers::Fixnum.serialize
88
+ Serializers.serialize("some string") # => output of Serializers::Base.serialize
89
+ ```
90
+
91
+ See also spec/example.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'choose_class/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "choose_class"
8
+ spec.version = ChooseClass::VERSION
9
+ spec.authors = ["Joel Plane"]
10
+ spec.email = ["joel.plane@gmail.com"]
11
+ spec.description = %q{Choose a class based on the value that would be passed to that class. Useful for implementing factory method pattern.}
12
+ spec.summary = %q{Choose subclass based on another objects class}
13
+ spec.homepage = "https://github.com/joelplane/choose_class"
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"
24
+ end
@@ -0,0 +1,9 @@
1
+ require "choose_class/version"
2
+ require "choose_class/util"
3
+ require "choose_class/class_naming_conventions"
4
+ require "choose_class/class_lookup"
5
+ require "choose_class/config"
6
+ require "choose_class/base"
7
+
8
+ module ChooseClass
9
+ end
@@ -0,0 +1,29 @@
1
+ module ChooseClass
2
+ module Base
3
+
4
+ module ClassMethods
5
+ def choose_class_config
6
+ @config ||= Config.new(self)
7
+ end
8
+
9
+ def choose_class value
10
+ class_name = class_name_for_value value
11
+ convention = choose_class_config.naming_convention
12
+ ClassLookup.new(convention).find_class_by_name class_name
13
+ end
14
+
15
+ private
16
+
17
+ # @return [String] class name
18
+ def class_name_for_value value
19
+ ChooseClass::Util.class_name_without_namespace value.class
20
+ end
21
+
22
+ end
23
+
24
+ def self.included base
25
+ base.extend ClassMethods
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module ChooseClass
2
+ class ClassLookup
3
+
4
+ # @param rules [ChooseClass::ClassNamingConventions]
5
+ def initialize rules
6
+ @rules = rules
7
+ end
8
+
9
+ # @param value_class_name [String]
10
+ def find_class_by_name value_class_name
11
+ class_name = factory_class_name value_class_name
12
+ begin
13
+ ChooseClass::Util.local_const_get(@rules.factory_namespace, class_name) ||
14
+ @rules.base_class
15
+ rescue NameError => e
16
+ if e.message =~ /#{@rules.factory_namespace.name}::#{class_name}$/
17
+ @rules.base_class
18
+ else
19
+ raise e # not the error we were expecting, so re-raise
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # @param value_class_name [String]
27
+ def factory_class_name value_class_name
28
+ @rules.factory_class_name_from_value_class_name value_class_name
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module ChooseClass
2
+ class ClassNamingConventions
3
+
4
+ attr_reader :base_class
5
+
6
+ # @param [Class] base_class
7
+ # @param [Array] rules
8
+ def initialize base_class, rules
9
+ @base_class = base_class
10
+ @rules = rules
11
+ set_default_rules
12
+ end
13
+
14
+ def factory_namespace
15
+ rule = @rules.detect do |rule|
16
+ rule.respond_to? :factory_namespace
17
+ end
18
+ rule.factory_namespace
19
+ end
20
+
21
+ def factory_class_name_from_value_class_name class_name
22
+ @rules.each do |rule|
23
+ if rule.respond_to? :factory_class_name_from_value_class_name
24
+ class_name = rule.factory_class_name_from_value_class_name class_name
25
+ end
26
+ end
27
+ class_name
28
+ end
29
+
30
+ private
31
+
32
+ def set_default_rules
33
+ if @rules.none?{|r|r.respond_to? :factory_namespace}
34
+ @rules << ChooseClass::ClassNamingConventions::Namespace.new(@base_class, nil)
35
+ end
36
+ if @rules.none?{|r|r.respond_to? :factory_class_name_from_value_class_name}
37
+ @rules << ChooseClass::ClassNamingConventions::Suffix.new(@base_class, "")
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+
44
+ rules_dir = File.expand_path('./class_naming_conventions', File.dirname(__FILE__))
45
+ Dir[File.join(rules_dir,"*.rb")].each do |file|
46
+ require file
47
+ end
@@ -0,0 +1,21 @@
1
+ module ChooseClass
2
+ class ClassNamingConventions
3
+ class Namespace
4
+
5
+ def initialize base_class, ns
6
+ @base_class = base_class
7
+ @namespace = ns
8
+ end
9
+
10
+ # same namespace as base class
11
+ def factory_namespace
12
+ if @namespace
13
+ @namespace
14
+ else
15
+ ChooseClass::Util.module_parent @base_class
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module ChooseClass
2
+ class ClassNamingConventions
3
+ class Suffix
4
+
5
+ def initialize base_class, suffix
6
+ @base_class = base_class
7
+ @suffix = suffix
8
+ end
9
+
10
+ def factory_class_name_from_value_class_name class_name
11
+ if @suffix
12
+ "#{class_name}#{@suffix}"
13
+ else
14
+ # TODO this path is dead code,
15
+ # but we probably want to resurrect it at some point.
16
+ "#{class_name}#{factory_class_name_suffix}"
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ # Eg.
23
+ # if base class name is "SerializerBase", returns "Serializer"
24
+ # if base class name is "Penguins", returns nil
25
+ # @return [String, nil]
26
+ def factory_class_name_suffix
27
+ match = class_name_without_namespace(@base_class).match(/(.*)Base$/)
28
+ match && match[1]
29
+ end
30
+
31
+ def class_name_without_namespace klass
32
+ ChooseClass::Util.class_name_without_namespace klass
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ module ChooseClass
2
+ class Config
3
+
4
+ def initialize base_class
5
+ @rules = []
6
+ @base_class = base_class
7
+ end
8
+
9
+ def namespace ns
10
+ @rules << ChooseClass::ClassNamingConventions::Namespace.new(@base_class, ns)
11
+ self
12
+ end
13
+
14
+ def with_suffix suffix
15
+ @rules << ChooseClass::ClassNamingConventions::Suffix.new(@base_class, suffix)
16
+ self
17
+ end
18
+
19
+ # @return [ChooseClass::ClassNamingConventions]
20
+ def naming_convention
21
+ ChooseClass::ClassNamingConventions.new @base_class, @rules
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,69 @@
1
+ module ChooseClass
2
+ module Util
3
+ class << self
4
+
5
+ # Copied from ActiveSupport Module#parent
6
+ def module_parent mod
7
+ module_parent_name(mod) ? constantize(module_parent_name(mod)) : Object
8
+ end
9
+
10
+ # Copied from ActiveSupport in Rails 3.
11
+ def constantize camel_cased_word
12
+ names = camel_cased_word.split('::')
13
+ names.shift if names.empty? || names.first.empty?
14
+
15
+ constant = Object
16
+ names.each do |name|
17
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
18
+ end
19
+ constant
20
+ end
21
+
22
+ # Copied/adapted from ActiveSupport Module#parents
23
+ def module_parents mod
24
+ parents = []
25
+ if (parent_name = module_parent_name mod)
26
+ parts = parent_name.split('::')
27
+ until parts.empty?
28
+ parents << constantize(parts * '::')
29
+ parts.pop
30
+ end
31
+ end
32
+ parents << Object unless parents.include? Object
33
+ parents
34
+ end
35
+
36
+ # @param klass [Class]
37
+ # @return [String]
38
+ def class_name_without_namespace klass
39
+ klass.name.split('::').last
40
+ end
41
+
42
+ # If the constant is not found, const_get will search on Object
43
+ # which might find a class or module we don't want - we only want modules
44
+ # inside the namespace given.
45
+ # This method wraps const_get, throwing away the result if it is not
46
+ # within the namespace.
47
+ # @param namespace [Class, Module]
48
+ # @param symbol [Symbol]
49
+ # @return [Class, Module, nil]
50
+ def local_const_get namespace, symbol
51
+ return nil unless namespace.const_defined?(symbol)
52
+ found_class = namespace.const_get(symbol)
53
+ if module_parents(found_class).include?(namespace)
54
+ found_class
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Copied from ActiveSupport Module#parent_name
63
+ def module_parent_name mod
64
+ mod.name =~ /::[^:]+\Z/ ? $`.freeze : nil
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module ChooseClass
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,15 @@
1
+ module ChooseClass
2
+ module Example1
3
+
4
+ class SerializerBase
5
+ include ChooseClass::Base
6
+ choose_class_config.namespace(Example1).with_suffix("Serializer")
7
+ end
8
+
9
+ # Specialisation class for Fixnum values
10
+ class FixnumSerializer
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module ChooseClass
2
+ module Example2
3
+
4
+ class SerializerBase
5
+ include ChooseClass::Base
6
+ choose_class_config.namespace(Example2).with_suffix("")
7
+ end
8
+
9
+ # Specialisation class for Fixnum values
10
+ # Does not have to be a subclass of SerializerBase, but is in this case.
11
+ class Fixnum < SerializerBase
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module ChooseClass
2
+ module Example3
3
+
4
+ module Serializers
5
+ class Base
6
+ include ChooseClass::Base
7
+ # not calling choose_class_config in this example
8
+ end
9
+
10
+ # Specialisation class for Fixnum values
11
+ class Fixnum
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require File.expand_path('../../lib/choose_class', File.dirname(__FILE__))
3
+ require File.expand_path('./example1', File.dirname(__FILE__))
4
+ require File.expand_path('./example2', File.dirname(__FILE__))
5
+ require File.expand_path('./example3', File.dirname(__FILE__))
6
+
7
+ module ChooseClass
8
+ describe "serializers example 1" do
9
+ describe ".choose_class" do
10
+
11
+ it "returns a specialised instance" do
12
+ Example1::SerializerBase.choose_class(1).new.should be_a Example1::FixnumSerializer
13
+ end
14
+
15
+ it "returns the generic instance when no specific class available" do
16
+ Example1::SerializerBase.choose_class("string").new.should be_a Example1::SerializerBase
17
+ end
18
+
19
+ end
20
+ end
21
+
22
+ describe "serializers example 2" do
23
+ describe ".choose_class" do
24
+
25
+ it "returns a specialised instance" do
26
+ Example2::SerializerBase.choose_class(1).new.should be_a Example2::Fixnum
27
+ end
28
+
29
+ it "returns the generic instance when no specific class available" do
30
+ Example2::SerializerBase.choose_class("string").new.should be_a Example2::SerializerBase
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ describe "serializers example 3" do
37
+ describe ".choose_class" do
38
+
39
+ it "returns a specialised instance" do
40
+ Example3::Serializers::Base.choose_class(1).new.should be_a Example3::Serializers::Fixnum
41
+ end
42
+
43
+ it "returns the generic instance when no specific class available" do
44
+ Example3::Serializers::Base.choose_class("string").new.should be_a Example3::Serializers::Base
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,6 @@
1
+ RSpec.configure do |config|
2
+ config.treat_symbols_as_metadata_keys_with_true_values = true
3
+ config.run_all_when_everything_filtered = true
4
+ config.filter_run :focus
5
+ config.order = 'random'
6
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: choose_class
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Joel Plane
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-07-31 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 9
29
+ segments:
30
+ - 1
31
+ - 3
32
+ version: "1.3"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rake
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: Choose a class based on the value that would be passed to that class. Useful for implementing factory method pattern.
64
+ email:
65
+ - joel.plane@gmail.com
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - .gitignore
74
+ - .rspec
75
+ - Gemfile
76
+ - LICENSE.txt
77
+ - README.md
78
+ - Rakefile
79
+ - choose_class.gemspec
80
+ - lib/choose_class.rb
81
+ - lib/choose_class/base.rb
82
+ - lib/choose_class/class_lookup.rb
83
+ - lib/choose_class/class_naming_conventions.rb
84
+ - lib/choose_class/class_naming_conventions/namespace.rb
85
+ - lib/choose_class/class_naming_conventions/suffix.rb
86
+ - lib/choose_class/config.rb
87
+ - lib/choose_class/util.rb
88
+ - lib/choose_class/version.rb
89
+ - spec/example/example1.rb
90
+ - spec/example/example2.rb
91
+ - spec/example/example3.rb
92
+ - spec/example/example_spec.rb
93
+ - spec/spec_helper.rb
94
+ homepage: https://github.com/joelplane/choose_class
95
+ licenses:
96
+ - MIT
97
+ post_install_message:
98
+ rdoc_options: []
99
+
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.7.2
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Choose subclass based on another objects class
127
+ test_files:
128
+ - spec/example/example1.rb
129
+ - spec/example/example2.rb
130
+ - spec/example/example3.rb
131
+ - spec/example/example_spec.rb
132
+ - spec/spec_helper.rb