modularity 0.6.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,148 @@
1
+ Modularity 2 - Traits and partial classes for Ruby
2
+ ==================================================
3
+
4
+ Modularity enhances Ruby's [`Module`] so it can be used traits and partial classes.
5
+ This allows very simple definition of meta-programming macros like the
6
+ `has_many` that you know from Rails.
7
+
8
+ Modularity also lets you organize large models into multiple source files
9
+ in a way that is less awkward than using modules.
10
+
11
+ Note that this is **Modularity 2**, which has a different syntax older version.
12
+ Modularity 1 users can use [a script to migrate their code](#migrating-from-modularity-1)
13
+ or use the [modularity1 branch](https://github.com/makandra/modularity/tree/modularity1).
14
+
15
+
16
+ Example 1: Easy meta-programming macros
17
+ ----------------------------------------
18
+
19
+ Ruby allows you to construct classes using meta-programming macros like
20
+ `acts_as_tree` or `has_many :items`. These macros will add methods,
21
+ callbacks, etc. to the calling class. However, right now Ruby (and Rails) makes it awkward to define
22
+ such macros in your project as part of your application domain.
23
+
24
+ Modularity allows you to extract common behaviour into reusable macros by defining traits with parameters.
25
+ Your macros can live in your application, allowing you to express your application domain in both classes
26
+ and macros.
27
+
28
+ Here is an example of a `strip_field` macro, which created setter methods that remove leading and trailing whitespace from newly assigned values:
29
+
30
+ # app/models/article.rb
31
+ class Article
32
+ include DoesStripFields[:name, :brand]
33
+ end
34
+
35
+ # app/models/shared/does_strip_fields.rb
36
+ module DoesStripFields
37
+ as_trait do |*fields|
38
+ fields.each do |field|
39
+ define_method("#{field}=") do |value|
40
+ self[field] = value.strip
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ We like to add `app/models/shared` and `app/controllers/shared` to the load paths of our Rails projects.
47
+ These are great places to store macros that are re-used from multiple classes.
48
+
49
+
50
+ Example 2: Mixins with class methods
51
+ ------------------------------------
52
+
53
+ Using a module to add both instance methods and class methods is
54
+ [very awkward](http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html).
55
+ Modularity does away with the clutter and lets you say this:
56
+
57
+ # app/models/model.rb
58
+ class Model
59
+ include Mixin
60
+ end
61
+
62
+ # app/models/mixin.rb
63
+ module Mixin
64
+ as_trait do
65
+ def instance_method
66
+ # ...
67
+ end
68
+ def self.class_method
69
+ # ..
70
+ end
71
+ end
72
+ end
73
+
74
+ `private` and `protected` will also work as expected when defining a trait.
75
+
76
+
77
+ Example 3: Splitting a model into multiple source files
78
+ -------------------------------------------------------
79
+
80
+ Models are often concerned with multiple themes like "authentication", "contact info" or "permissions", each requiring
81
+ a couple of validations and callbacks here, and some method there. Modularity lets you organize your model into multiple
82
+ partial classes, so each file can deal with a single aspect of your model:
83
+
84
+ # app/models/user.rb
85
+ class User < ActiveRecord::Base
86
+ include User::DoesAuthentication
87
+ include User::DoesAddress
88
+ end
89
+
90
+ # app/models/user/does_authentication.rb
91
+ module User::DoesAuthentication
92
+ as_trait do
93
+ # methods, validations, etc. regarding usernames and passwords go here
94
+ end
95
+ end
96
+
97
+ # app/models/user/does_permissions.rb
98
+ module User::DoesPermissions
99
+ as_trait do
100
+ # methods, validations, etc. regarding contact information go here
101
+ end
102
+ end
103
+
104
+ Some criticism has been raised for splitting large models into files like this.
105
+ Essentially, even though have an easier time navigating your code, you will still
106
+ have one giant model with many side effects.
107
+
108
+ There are [many better ways](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/)
109
+ to decompose a huge Ruby class.
110
+
111
+
112
+ Installation
113
+ ------------
114
+
115
+ Add the following to your `Gemfile`:
116
+
117
+ gem 'modularity', '>=2'
118
+
119
+ Now run `bundle install`.
120
+
121
+
122
+ Migrating from Modularity 1
123
+ ---------------------------
124
+
125
+ If you have been using Modularity 1 with the `does` syntax, we provide a script to migrate your Ruby project
126
+ automatically.
127
+
128
+ 1. Make sure your project has tests and you have a backup of your files (or pushed your commits to Git)
129
+
130
+ 2. From your project directory, do this:
131
+
132
+ find . -name "*.rb" | migrate-modularity1-to-modularity2
133
+
134
+ 3. The script will rename your files and change your code. It will also syntax-check your files after conversion
135
+ (since the script is not perfect).
136
+
137
+ 4. Check the diff to see what the script has done.
138
+
139
+ 5. Run tests to see if everything still works.
140
+
141
+
142
+
143
+
144
+ Credits
145
+ -------
146
+
147
+ Henning Koch from [makandra.com](http://makandra.com/)
148
+
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__)) + '/../lib/modularity/migrator'
4
+
5
+ ARGV.each do |path|
6
+ Modularity::Migrator.migrate(path)
7
+ end
data/lib/modularity.rb CHANGED
@@ -1,2 +1 @@
1
- require 'modularity/does'
2
-
1
+ require 'modularity/as_trait'
@@ -1,9 +1,40 @@
1
1
  module Modularity
2
+
3
+ class ParametrizedTrait < Module
4
+
5
+ def initialize(blank_trait, args)
6
+ @args = args
7
+ @macro = blank_trait.instance_variable_get(:@modularity_macro)
8
+ include(blank_trait)
9
+ end
10
+
11
+ def included(base)
12
+ base.class_exec(*@args, &@macro)
13
+ end
14
+
15
+ end
16
+
2
17
  module AsTrait
3
- def as_trait(&block)
4
- @trait_macro = block
18
+
19
+ def as_trait(&macro)
20
+
21
+ @modularity_macro = macro
22
+
23
+ def self.included(base)
24
+ unless base.is_a?(ParametrizedTrait)
25
+ base.class_exec(&@modularity_macro)
26
+ end
27
+
28
+ end
29
+
30
+ def self.[](*args)
31
+ blank_trait = self
32
+ ParametrizedTrait.new(blank_trait, args)
33
+ end
34
+
5
35
  end
36
+
6
37
  end
7
38
  end
8
39
 
9
- Object.send :include, Modularity::AsTrait
40
+ Module.send(:include, Modularity::AsTrait)
@@ -0,0 +1,53 @@
1
+ gem 'activesupport'
2
+ require 'active_support/all'
3
+ require 'fileutils'
4
+
5
+ module Modularity
6
+ class Migrator
7
+ class << self
8
+
9
+ def migrate(old_path)
10
+ old_path = File.expand_path(old_path)
11
+ new_path = fix_filename(old_path)
12
+ rename(old_path, new_path) unless old_path == new_path
13
+ old_code = File.read(new_path)
14
+ new_code = fix_code(old_code)
15
+ rewrite_file(new_path, new_code) unless old_code == new_code
16
+ puts "Migrated #{old_path}"
17
+ `ruby -c #{new_path}`
18
+ new_code
19
+ end
20
+
21
+ private
22
+
23
+ def fix_filename(path)
24
+ path = File.expand_path(path)
25
+ new_path = path.sub(/\/([^\/]+)_trait\.rb$/, '/does_\\1.rb')
26
+ new_path
27
+ end
28
+
29
+ def fix_code(code)
30
+ code = code.gsub(/module (.*?)([A-Za-z0-9_]+)Trait\b/, 'module \\1Does\\2')
31
+ code = code.gsub(/does ['":]([A-Za-z0-9\_\/]+)(?:'|"|$)(?:,\s*(.*)$)?/) do
32
+ trait_path = $1
33
+ parameters = $2
34
+ trait_path = trait_path.sub(/([A-Za-z0-9\_]+)$/, 'does_\\1')
35
+ trait_class = trait_path.camelize # don't use classify, it removes plurals!
36
+ substituted = "include #{trait_class}"
37
+ substituted << "[#{parameters}]" if parameters
38
+ substituted
39
+ end
40
+ code
41
+ end
42
+
43
+ def rename(old, new)
44
+ FileUtils.mv(old, new)
45
+ end
46
+
47
+ def rewrite_file(path, content)
48
+ File.open(path, "w") { |file| file.write(content) }
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module Modularity
2
- VERSION = '0.6.1'
2
+ VERSION = '2.0.0'
3
3
  end
data/modularity.gemspec CHANGED
@@ -18,5 +18,6 @@ Gem::Specification.new do |s|
18
18
 
19
19
  s.add_development_dependency('rake')
20
20
  s.add_development_dependency('rspec', '<2')
21
+ s.add_development_dependency('rspec_candy')
21
22
 
22
23
  end
@@ -0,0 +1,210 @@
1
+ require 'spec_helper'
2
+
3
+ describe Modularity::AsTrait do
4
+
5
+ describe '.included' do
6
+
7
+ before :each do
8
+ @doing_class = Class.new
9
+ end
10
+
11
+ describe 'without parameters' do
12
+
13
+ it "applies the trait macro of the given module" do
14
+
15
+ module DoesSome
16
+ as_trait do
17
+ some_trait_included
18
+ end
19
+ end
20
+
21
+ @doing_class.should_receive(:some_trait_included)
22
+
23
+ @doing_class.class_eval do
24
+ include DoesSome
25
+ end
26
+
27
+ end
28
+
29
+ it "applies the trait macro of the given namespaced module" do
30
+
31
+ module Some
32
+ module DoesChild
33
+ as_trait do
34
+ some_child_trait_included
35
+ end
36
+ end
37
+ end
38
+
39
+ @doing_class.should_receive(:some_child_trait_included)
40
+
41
+ @doing_class.class_eval do
42
+ include Some::DoesChild
43
+ end
44
+
45
+ end
46
+
47
+ it "lets a trait define methods with different visibility" do
48
+
49
+ module DoesVisibility
50
+ as_trait do
51
+ def public_method_from_trait
52
+ end
53
+ protected
54
+ def protected_method_from_trait
55
+ end
56
+ private
57
+ def private_method_from_trait
58
+ end
59
+ end
60
+ end
61
+
62
+ @doing_class.class_eval do
63
+ include DoesVisibility
64
+ end
65
+
66
+ instance = @doing_class.new
67
+
68
+ instance.public_methods.collect(&:to_s).should include("public_method_from_trait")
69
+ instance.protected_methods.collect(&:to_s).should include("protected_method_from_trait")
70
+ instance.private_methods.collect(&:to_s).should include("private_method_from_trait")
71
+
72
+ end
73
+
74
+ it 'appends methods outside the trait macro' do
75
+
76
+ module HybridModule
77
+
78
+ as_trait do
79
+ define_method :trait_method do
80
+ end
81
+ end
82
+
83
+ def vanilla_method
84
+ end
85
+
86
+ end
87
+
88
+ @doing_class.class_eval do
89
+ include HybridModule
90
+ end
91
+
92
+ instance = @doing_class.new
93
+
94
+ instance.should respond_to(:trait_method)
95
+ instance.should respond_to(:vanilla_method)
96
+
97
+ end
98
+
99
+ it 'applies multiple trait macros' do
100
+
101
+ module FirstTrait
102
+ as_trait do
103
+ define_method :first do
104
+ end
105
+ end
106
+ end
107
+
108
+ module SecondTrait
109
+ as_trait do
110
+ define_method :second do
111
+ end
112
+ end
113
+ end
114
+
115
+ @doing_class.class_eval do
116
+ include FirstTrait
117
+ include SecondTrait
118
+ end
119
+
120
+ instance = @doing_class.new
121
+
122
+ instance.should respond_to(:first)
123
+ instance.should respond_to(:second)
124
+
125
+ end
126
+
127
+ end
128
+
129
+ describe "with parameters" do
130
+
131
+ it "it applies a trait macro with parameters" do
132
+
133
+ module DoesCallMethod
134
+ as_trait do |field|
135
+ send(field)
136
+ end
137
+ end
138
+
139
+ @doing_class.should_receive(:foo)
140
+ @doing_class.class_eval do
141
+ include DoesCallMethod[:foo]
142
+ end
143
+
144
+ end
145
+
146
+ it "facilitates metaprogramming acrobatics" do
147
+
148
+ module DoesDefineConstantMethod
149
+ as_trait do |name, return_value|
150
+ define_method name do
151
+ return_value
152
+ end
153
+ end
154
+ end
155
+
156
+ @doing_class.class_eval do
157
+ include DoesDefineConstantMethod["some_method", "some_return_value"]
158
+ end
159
+
160
+ instance = @doing_class.new
161
+ instance.should respond_to(:some_method)
162
+ instance.some_method.should == "some_return_value"
163
+ end
164
+
165
+ it "allies to call an unparametrized trait macro with an empty parameter list" do
166
+
167
+ module DoesSome
168
+ as_trait do
169
+ some_trait_included
170
+ end
171
+ end
172
+
173
+ @doing_class.should_receive(:some_trait_included)
174
+
175
+ @doing_class.class_eval do
176
+ include DoesSome[]
177
+ end
178
+
179
+ end
180
+
181
+ it 'appends methods outside the trait macro' do
182
+
183
+ module HybridModuleWithParameters
184
+
185
+ as_trait do |name|
186
+ define_method name do
187
+ end
188
+ end
189
+
190
+ def vanilla_method
191
+ end
192
+
193
+ end
194
+
195
+ @doing_class.class_eval do
196
+ include HybridModuleWithParameters[:trait_method]
197
+ end
198
+
199
+ instance = @doing_class.new
200
+
201
+ instance.should respond_to(:trait_method)
202
+ instance.should respond_to(:vanilla_method)
203
+
204
+ end
205
+
206
+ end
207
+
208
+ end
209
+
210
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ require 'modularity/migrator' # this is an optional file and thus not loaded automatically
4
+
5
+ describe Modularity::Migrator do
6
+
7
+ subject do
8
+ Modularity::Migrator
9
+ end
10
+
11
+ describe '.fix_filename' do
12
+
13
+ it 'should replace the suffix _trait with a prefix does' do
14
+ subject.send(:fix_filename, '/path/to/search_trait.rb').should == '/path/to/does_search.rb'
15
+ end
16
+
17
+ it "should not rename files that don't end in _trait.rb" do
18
+ subject.send(:fix_filename, '/path/to/search.rb').should == '/path/to/search.rb'
19
+ end
20
+
21
+ end
22
+
23
+ describe '.fix_code' do
24
+
25
+ it 'renames a module FooBarTrait to DoesFooBar' do
26
+
27
+ old_code = <<-RUBY
28
+ module Namespace::FooBarTrait
29
+ as_trait do
30
+ define_method :foo do
31
+ end
32
+ define_method :bar do
33
+ end
34
+ end
35
+ end
36
+ RUBY
37
+
38
+ new_code = <<-RUBY
39
+ module Namespace::DoesFooBar
40
+ as_trait do
41
+ define_method :foo do
42
+ end
43
+ define_method :bar do
44
+ end
45
+ end
46
+ end
47
+ RUBY
48
+
49
+ subject.send(:fix_code, old_code).should == new_code
50
+ end
51
+
52
+ it "does not rename modules that aren't traits" do
53
+
54
+ old_code = <<-RUBY
55
+ module Namespace::FooBar
56
+ def foo
57
+ end
58
+ def bar
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ subject.send(:fix_code, old_code).should == old_code
64
+ end
65
+
66
+ it 'replaces does calls with include' do
67
+
68
+ old_code = <<-RUBY
69
+ class User < ActiveRecord::Base
70
+ does 'user/search'
71
+ does 'user/account_settings'
72
+ does 'trashable'
73
+ end
74
+ RUBY
75
+
76
+ new_code = <<-RUBY
77
+ class User < ActiveRecord::Base
78
+ include User::DoesSearch
79
+ include User::DoesAccountSettings
80
+ include DoesTrashable
81
+ end
82
+ RUBY
83
+
84
+ subject.send(:fix_code, old_code).should == new_code
85
+ end
86
+
87
+ it 'puts does parameters into square brackets' do
88
+
89
+ old_code = <<-RUBY
90
+ class User < ActiveRecord::Base
91
+ does 'flag', :active, :default => true
92
+ does 'record/search', :field => :email
93
+ end
94
+ RUBY
95
+
96
+ new_code = <<-RUBY
97
+ class User < ActiveRecord::Base
98
+ include DoesFlag[:active, :default => true]
99
+ include Record::DoesSearch[:field => :email]
100
+ end
101
+ RUBY
102
+
103
+ subject.send(:fix_code, old_code).should == new_code
104
+
105
+ end
106
+
107
+ end
108
+
109
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  $: << File.join(File.dirname(__FILE__), "/../lib" )
2
2
 
3
3
  require "#{File.dirname(__FILE__)}/../lib/modularity"
4
+ require 'rspec_candy/all'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modularity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,12 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-23 00:00:00.000000000 +02:00
12
+ date: 2014-01-08 00:00:00.000000000 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
17
- requirement: &23087620 !ruby/object:Gem::Requirement
17
+ requirement: &22585700 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :development
24
24
  prerelease: false
25
- version_requirements: *23087620
25
+ version_requirements: *22585700
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rspec
28
- requirement: &23087020 !ruby/object:Gem::Requirement
28
+ requirement: &22585200 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - <
@@ -33,26 +33,38 @@ dependencies:
33
33
  version: '2'
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *23087020
36
+ version_requirements: *22585200
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec_candy
39
+ requirement: &22584740 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *22584740
37
48
  description: Traits and partial classes for Ruby
38
49
  email: github@makandra.de
39
- executables: []
50
+ executables:
51
+ - migrate-modularity1-to-modularity2
40
52
  extensions: []
41
53
  extra_rdoc_files: []
42
54
  files:
43
55
  - .gitignore
44
56
  - Gemfile
45
57
  - MIT-LICENSE
46
- - README.rdoc
58
+ - README.md
47
59
  - Rakefile
60
+ - bin/migrate-modularity1-to-modularity2
48
61
  - lib/modularity.rb
49
62
  - lib/modularity/as_trait.rb
50
- - lib/modularity/does.rb
51
- - lib/modularity/inflector.rb
63
+ - lib/modularity/migrator.rb
52
64
  - lib/modularity/version.rb
53
65
  - modularity.gemspec
54
- - spec/as_trait_spec.rb
55
- - spec/does_spec.rb
66
+ - spec/modularity/as_trait_spec.rb
67
+ - spec/modularity/migrator_spec.rb
56
68
  - spec/rcov.opts
57
69
  - spec/spec.opts
58
70
  - spec/spec_helper.rb
@@ -69,21 +81,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
81
  - - ! '>='
70
82
  - !ruby/object:Gem::Version
71
83
  version: '0'
84
+ segments:
85
+ - 0
86
+ hash: 2498377129858303393
72
87
  required_rubygems_version: !ruby/object:Gem::Requirement
73
88
  none: false
74
89
  requirements:
75
90
  - - ! '>='
76
91
  - !ruby/object:Gem::Version
77
92
  version: '0'
93
+ segments:
94
+ - 0
95
+ hash: 2498377129858303393
78
96
  requirements: []
79
97
  rubyforge_project:
80
- rubygems_version: 1.6.2
98
+ rubygems_version: 1.3.9.5
81
99
  signing_key:
82
100
  specification_version: 3
83
101
  summary: Traits and partial classes for Ruby
84
- test_files:
85
- - spec/as_trait_spec.rb
86
- - spec/does_spec.rb
87
- - spec/rcov.opts
88
- - spec/spec.opts
89
- - spec/spec_helper.rb
102
+ test_files: []
data/README.rdoc DELETED
@@ -1,106 +0,0 @@
1
- = modularity - Traits and partial classes for Ruby
2
-
3
- Modularity provides traits and partial classes for Ruby.
4
- This lets you organize large models into multiple source files.
5
- It also allows very simple definition of meta-programming macros,
6
- as you might now from <tt>acts_as_something</tt> type of plugins,
7
- or the macros Rails provides for your models.
8
-
9
- Modularity traits are to your models what partials are for your Rails views.
10
-
11
- == Example 1: Splitting a model into multiple source files
12
-
13
- Models are often concerned with multiple themes like "authentication", "contact info" or "permissions", each requiring
14
- a couple of validations and callbacks here, and some method there. Modularity lets you organize your model into multiple
15
- partial classes, so each file can deal with a single aspect of your model:
16
-
17
- # app/models/user.rb
18
- class User < ActiveRecord::Base
19
- does "user/authentication"
20
- does "user/address"
21
- end
22
-
23
- # app/models/user/authentication_trait.rb
24
- module User::AuthenticationTrait
25
- as_trait do
26
- # methods, validations, etc. regarding usernames and passwords go here
27
- end
28
- end
29
-
30
- # app/models/user/permissions_trait.rb
31
- module User::PermissionsTrait
32
- as_trait do
33
- # methods, validations, etc. regarding contact information go here
34
- end
35
- end
36
-
37
- == Example 2: Easy meta-programming macros
38
-
39
- Ruby allows you to construct classes using meta-programming macros like <tt>acts_as_tree</tt> or <tt>has_many :items</tt>.
40
- These macros will add methods, callbacks, etc. to the calling class. Hoever, right now Ruby (and Rails) makes it awkward to define
41
- such macros in your project as part of your application domain.
42
-
43
- Modularity allows you to extract common behaviour into reusable macros by defining traits with parameters. Your macros can live in your
44
- application, allowing you to express your application domain in both classes and macros.
45
-
46
- Here is an example of a <tt>strip_field</tt> macro, which created setter methods that remove leading and trailing whitespace from newly assigned values:
47
-
48
- # app/models/article.rb
49
- class Article
50
- does "strip_fields", :name, :brand
51
- end
52
-
53
- # app/models/shared/strip_fields_trait.rb
54
- module StripFieldsTrait
55
- as_trait do |*fields|
56
- fields.each do |field|
57
- define_method("#{field}=") do |value|
58
- self[field] = value.strip
59
- end
60
- end
61
- end
62
- end
63
-
64
- We like to add <tt>app/models/shared</tt> and <tt>app/controllers/shared</tt> to the load paths of our Rails projects. These are great places to store macros
65
- that are re-used from multiple classes.
66
-
67
- == Example 3: Mixins with class methods
68
-
69
- Using a module to add both instance methods and class methods is {very awkward}[http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html].
70
- Modularity does away with the clutter and lets you say this:
71
-
72
- # app/models/model.rb
73
- class Model
74
- does "mixin"
75
- end
76
-
77
- # app/models/mixin_trait.rb
78
- module MixinTrait
79
- as_trait do
80
- def instance_method
81
- # ...
82
- end
83
- def self.class_method
84
- # ..
85
- end
86
- end
87
-
88
- <tt>private</tt> and <tt>protected</tt> will also work as expected when defining a trait.
89
-
90
- == Installation
91
-
92
- sudo gem install modularity
93
-
94
-
95
- == Note if you're still on Ruby 1.8.6
96
-
97
- Modularity requires Ruby 1.8.7. Earlier versions are missing <tt>class_exec</tt>. You might be able to hack in <tt>class_exec</tt>
98
- using {this}[http://github.com/brynary/rspec/blob/f80d61a399b34f58084a378c85a43a95ff484619/lib/spec/extensions/instance_exec.rb] as a guide, but it's not pretty.
99
-
100
- == Credits
101
-
102
- Henning Koch
103
-
104
- {makandra.com}[http://makandra.com/]
105
-
106
- {gem-session.com}[http://gem-session.com/]
@@ -1,23 +0,0 @@
1
- require 'modularity/inflector'
2
- require 'modularity/as_trait'
3
-
4
- module Modularity
5
- module Does
6
-
7
- def self.included(base)
8
- base.extend ClassMethods
9
- end
10
-
11
- module ClassMethods
12
- def does(trait_name, *args)
13
- trait_name = "#{Modularity::Inflector.camelize(trait_name.to_s)}Trait"
14
- trait = Modularity::Inflector.constantize(trait_name)
15
- macro = trait.instance_variable_get("@trait_macro") or raise "Missing trait directive in #{trait_name}"
16
- class_exec(*args, &macro)
17
- end
18
- end
19
-
20
- end
21
- end
22
-
23
- Object.send :include, Modularity::Does
@@ -1,60 +0,0 @@
1
- # These methods are backported from Rails so modularity works with plain Ruby.
2
-
3
- module Modularity
4
- class Inflector
5
- class << self
6
-
7
- # File activesupport/lib/active_support/inflector.rb, line 178
8
- def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
9
- if first_letter_in_uppercase
10
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
11
- else
12
- lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
13
- end
14
- end
15
-
16
-
17
- if Module.method(:const_get).arity == 1
18
- # Tries to find a constant with the name specified in the argument string:
19
- #
20
- # "Module".constantize # => Module
21
- # "Test::Unit".constantize # => Test::Unit
22
- #
23
- # The name is assumed to be the one of a top-level constant, no matter whether
24
- # it starts with "::" or not. No lexical context is taken into account:
25
- #
26
- # C = 'outside'
27
- # module M
28
- # C = 'inside'
29
- # C # => 'inside'
30
- # "C".constantize # => 'outside', same as ::C
31
- # end
32
- #
33
- # NameError is raised when the name is not in CamelCase or the constant is
34
- # unknown.
35
- def constantize(camel_cased_word)
36
- names = camel_cased_word.split('::')
37
- names.shift if names.empty? || names.first.empty?
38
-
39
- constant = Object
40
- names.each do |name|
41
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
42
- end
43
- constant
44
- end
45
- else
46
- def constantize(camel_cased_word) #:nodoc:
47
- names = camel_cased_word.split('::')
48
- names.shift if names.empty? || names.first.empty?
49
-
50
- constant = Object
51
- names.each do |name|
52
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
53
- end
54
- constant
55
- end
56
- end
57
-
58
- end
59
- end
60
- end
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Trait
4
- as_trait do
5
- "hi world"
6
- end
7
- end
8
-
9
- module ParametrizedTrait
10
- as_trait do |name|
11
- "hi, #{name}"
12
- end
13
- end
14
-
15
- describe Modularity::AsTrait do
16
-
17
- describe 'as_trait' do
18
-
19
- it "should let modules save a proc upon loading" do
20
- Trait.instance_variable_get("@trait_macro").call.should == "hi world"
21
- end
22
-
23
- it "should let modules save a proc with parameters upon loading" do
24
- ParametrizedTrait.instance_variable_get("@trait_macro").call("jean").should == "hi, jean"
25
- end
26
-
27
- end
28
-
29
- end
data/spec/does_spec.rb DELETED
@@ -1,93 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module SomeTrait
4
- as_trait do
5
- some_trait_included
6
- end
7
- end
8
-
9
- module Some
10
- module ChildTrait
11
- as_trait do
12
- some_child_trait_included
13
- end
14
- end
15
- end
16
-
17
- module CallMethodTrait
18
- as_trait do |field|
19
- send(field)
20
- end
21
- end
22
-
23
- module VisibilityTrait
24
- as_trait do
25
- def public_method_from_trait
26
- end
27
- protected
28
- def protected_method_from_trait
29
- end
30
- private
31
- def private_method_from_trait
32
- end
33
- end
34
- end
35
-
36
- module DefineConstantMethodTrait
37
- as_trait do |name, return_value|
38
- define_method name do
39
- return_value
40
- end
41
- end
42
- end
43
-
44
- class Doer
45
- end
46
-
47
- describe Modularity::AsTrait do
48
-
49
- describe 'does' do
50
-
51
- it "should apply the named module" do
52
- Doer.should_receive(:some_trait_included)
53
- Doer.class_eval do
54
- does "some"
55
- end
56
- end
57
-
58
- it "should apply a namespaced module, using slash-notation like require" do
59
- Doer.should_receive(:some_child_trait_included)
60
- Doer.class_eval do
61
- does "some/child"
62
- end
63
- end
64
-
65
- it "should class_eval the as_trait proc on the doer" do
66
- Doer.should_receive(:foo)
67
- Doer.class_eval do
68
- does "call_method", :foo
69
- end
70
- end
71
-
72
- it "should allow the trait to define methods with different visibility" do
73
- Doer.class_eval do
74
- does "visibility"
75
- end
76
- instance = Doer.new
77
- instance.public_methods.collect(&:to_s).should include("public_method_from_trait")
78
- instance.protected_methods.collect(&:to_s).should include("protected_method_from_trait")
79
- instance.private_methods.collect(&:to_s).should include("private_method_from_trait")
80
- end
81
-
82
- it "should allow the trait to perform metaprogramming acrobatics" do
83
- Doer.class_eval do
84
- does "define_constant_method", "some_method", "some_return_value"
85
- end
86
- instance = Doer.new
87
- instance.should respond_to(:some_method)
88
- instance.some_method.should == "some_return_value"
89
- end
90
-
91
- end
92
-
93
- end