modularity 0.6.1 → 2.0.0

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/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