diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..ffce818
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,46 @@
+---
+name: Tests
+'on':
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+jobs:
+ test:
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - ruby: 2.5.7
+ gemfile: Gemfile
+ - ruby: 2.6.10
+ gemfile: Gemfile
+ - ruby: 2.7.4
+ gemfile: Gemfile
+ - ruby: 3.0.2
+ gemfile: Gemfile
+ - ruby: 3.1.2
+ gemfile: Gemfile
+ - ruby: 3.2.1
+ gemfile: Gemfile
+ - ruby: 3.3.6
+ gemfile: Gemfile
+ - ruby: 3.4.1
+ gemfile: Gemfile
+ env:
+ BUNDLE_GEMFILE: "${{ matrix.gemfile }}"
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "${{ matrix.ruby }}"
+ - name: Bundle
+ run: |
+ gem install bundler:2.3.27
+ bundle install --no-deployment
+ - name: Run tests
+ run: bundle exec rspec
diff --git a/.gitignore b/.gitignore
index 6d88d9c..8c0f972 100755
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,3 @@ doc
pkg
*.gem
.idea
-Gemfile.lock
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..c99d2e7
--- /dev/null
+++ b/.rspec
@@ -0,0 +1 @@
+--require spec_helper
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000..35d16fb
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.5.7
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..908d6e4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,45 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+
+## Unreleased
+
+### Breaking changes
+
+### Compatible changes
+
+- Add support for Ruby 3.3
+- Add support for Ruby 3.4
+
+## 3.2.0 - 2023-03-01
+
+### Compatible changes
+
+- Add support for Ruby 3.2
+
+
+## 3.1.0 - 2022-06-01
+
+### Compatible changes
+
+- Add support for separation of positional and keyword arguments in ruby 3
+
+
+## 3.0.1 - 2022-03-09
+
+### Compatible changes
+
+- Activate Rubygems MFA
+
+
+## 3.0.0 - 2021-08-24
+
+### Breaking changes
+
+- Removed migration guide for modularity version 1.
+- Removed support for Ruby < `2.5.0`.
+
+### Compatible changes
+
+- Added this CHANGELOG file.
diff --git a/Gemfile b/Gemfile
index c80ee36..f0d494f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,8 @@
-source "http://rubygems.org"
+source 'http://rubygems.org'
gemspec
+
+gem 'rake'
+gem 'rspec'
+gem 'pry-byebug'
+gem 'gemika'
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..28e2709
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,47 @@
+PATH
+ remote: .
+ specs:
+ modularity (3.2.0)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ byebug (11.1.3)
+ coderay (1.1.3)
+ diff-lcs (1.4.4)
+ gemika (0.8.1)
+ method_source (1.0.0)
+ pry (0.13.1)
+ coderay (~> 1.1)
+ method_source (~> 1.0)
+ pry-byebug (3.9.0)
+ byebug (~> 11.0)
+ pry (~> 0.13.0)
+ rake (13.0.6)
+ rspec (3.10.0)
+ rspec-core (~> 3.10.0)
+ rspec-expectations (~> 3.10.0)
+ rspec-mocks (~> 3.10.0)
+ rspec-core (3.10.1)
+ rspec-support (~> 3.10.0)
+ rspec-expectations (3.10.1)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.10.0)
+ rspec-mocks (3.10.2)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.10.0)
+ rspec-support (3.10.2)
+
+PLATFORMS
+ ruby
+ x86_64-linux
+
+DEPENDENCIES
+ gemika
+ modularity!
+ pry-byebug
+ rake
+ rspec
+
+BUNDLED WITH
+ 2.3.27
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..646e18a
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2009 Henning Koch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/MIT-LICENSE b/MIT-LICENSE
deleted file mode 100755
index 30adc46..0000000
--- a/MIT-LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2009 Henning Koch
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..8937d56
--- /dev/null
+++ b/README.md
@@ -0,0 +1,128 @@
+[](https://github.com/makandra/modularity/actions)
+
+# Modularity - Traits and partial classes for Ruby
+
+Modularity enhances Ruby's [`Module`](http://apidock.com/ruby/Module) so it can be used traits and partial classes.
+This allows very simple definition of meta-programming macros like the
+`has_many` that you know from Rails.
+
+Modularity also lets you organize large models into multiple source files
+in a way that is less awkward than using modules.
+
+## Installation
+
+Add the following to your `Gemfile`:
+
+```ruby
+gem 'modularity'
+```
+
+Now run `bundle install`.
+
+## Example 1: Easy meta-programming macros
+
+Ruby allows you to construct classes using meta-programming macros like
+`acts_as_tree` or `has_many :items`. These macros will add methods,
+callbacks, etc. to the calling class. However, right now Ruby (and Rails) makes it awkward to define
+such macros in your project as part of your application domain.
+
+Modularity allows you to extract common behaviour into reusable macros by defining traits with parameters.
+Your macros can live in your application, allowing you to express your application domain in both classes
+and macros.
+
+Here is an example of a `strip_field` macro, which created setter methods that remove leading and trailing whitespace from newly assigned values:
+
+```ruby
+# app/models/article.rb
+class Article < ActiveRecord::Base
+ include DoesStripFields[:name, :brand]
+end
+
+# app/models/shared/does_strip_fields.rb
+module DoesStripFields
+ as_trait do |*fields|
+ fields.each do |field|
+ define_method("#{field}=") do |value|
+ self[field] = value.strip
+ end
+ end
+ end
+end
+```
+
+Notice the `as_trait` block.
+
+We like to add `app/models/shared` and `app/controllers/shared` to the load paths of our Rails projects.
+These are great places to store macros that are re-used from multiple classes.
+
+## Example 2: Mixins with class methods
+
+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).
+Modularity does away with the clutter and lets you say this:
+
+```ruby
+# app/models/model.rb
+class Model
+ include Mixin
+end
+
+# app/models/mixin.rb
+module Mixin
+ as_trait do
+ def instance_method
+ # ...
+ end
+ def self.class_method
+ # ..
+ end
+ end
+end
+```
+
+`private` and `protected` will also work as expected when defining a trait.
+
+## Example 3: Splitting a model into multiple source files
+
+Models are often concerned with multiple themes like "authentication", "contact info" or "permissions", each requiring
+a couple of validations and callbacks here, and some method there. Modularity lets you organize your model into multiple
+partial classes, so each file can deal with a single aspect of your model:
+
+```ruby
+# app/models/user.rb
+class User < ActiveRecord::Base
+ include DoesAuthentication
+ include DoesPermissions
+end
+
+# app/models/user/does_authentication.rb
+module User::DoesAuthentication
+ as_trait do
+ # methods, validations, etc. regarding usernames and passwords go here
+ end
+end
+
+# app/models/user/does_permissions.rb
+module User::DoesPermissions
+ as_trait do
+ # methods, validations, etc. regarding contact information go here
+ end
+end
+```
+
+Some criticism has been raised for splitting large models into files like this.
+Essentially, even though have an easier time navigating your code, you will still
+have one giant model with many side effects.
+
+There are [many better ways](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/)
+to decompose a huge Ruby class.
+
+## Development
+
+* Install Bundler 2 `gem install bundler:2.2.22` and run `bundle install` to have a working development setup.
+* Running tests for the current Ruby version: `bundle exec rake`
+* Running tests for all supported Ruby version: Push the changes to Github in a feature branch, open a merge request and have a look at the test matrix in Github actions
+
+## Credits
+
+Henning Koch from [makandra.com](http://makandra.com/)
diff --git a/README.rdoc b/README.rdoc
deleted file mode 100755
index 3190ea7..0000000
--- a/README.rdoc
+++ /dev/null
@@ -1,107 +0,0 @@
-= modularity - Traits and partial classes for Ruby
-
-Modularity provides traits and partial classes for Ruby.
-This allows very simple definition of meta-programming macros,
-as you might now from acts_as_something type of plugins,
-or the macros Rails provides for your models. This also lets you organize
-large models into multiple source files in a way that is less awkward
-than using modules.
-
-Modularity traits are to your models what partials are for your Rails views.
-
-== Example 1: Easy meta-programming macros
-
-Ruby allows you to construct classes using meta-programming macros like acts_as_tree or has_many :items.
-These macros will add methods, callbacks, etc. to the calling class. However, right now Ruby (and Rails) makes it awkward to define
-such macros in your project as part of your application domain.
-
-Modularity allows you to extract common behaviour into reusable macros by defining traits with parameters. Your macros can live in your
-application, allowing you to express your application domain in both classes and macros.
-
-Here is an example of a strip_field macro, which created setter methods that remove leading and trailing whitespace from newly assigned values:
-
- # app/models/article.rb
- class Article
- does "strip_fields", :name, :brand
- end
-
- # app/models/shared/strip_fields_trait.rb
- module StripFieldsTrait
- as_trait do |*fields|
- fields.each do |field|
- define_method("#{field}=") do |value|
- self[field] = value.strip
- end
- end
- end
- end
-
-We like to add app/models/shared and app/controllers/shared to the load paths of our Rails projects. These are great places to store macros
-that are re-used from multiple classes.
-
-== Example 2: Mixins with class methods
-
-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].
-Modularity does away with the clutter and lets you say this:
-
- # app/models/model.rb
- class Model
- does "mixin"
- end
-
- # app/models/mixin_trait.rb
- module MixinTrait
- as_trait do
- def instance_method
- # ...
- end
- def self.class_method
- # ..
- end
- end
-
-private and protected will also work as expected when defining a trait.
-
-== Example 3: Splitting a model into multiple source files
-
-Models are often concerned with multiple themes like "authentication", "contact info" or "permissions", each requiring
-a couple of validations and callbacks here, and some method there. Modularity lets you organize your model into multiple
-partial classes, so each file can deal with a single aspect of your model:
-
- # app/models/user.rb
- class User < ActiveRecord::Base
- does "user/authentication"
- does "user/address"
- end
-
- # app/models/user/authentication_trait.rb
- module User::AuthenticationTrait
- as_trait do
- # methods, validations, etc. regarding usernames and passwords go here
- end
- end
-
- # app/models/user/permissions_trait.rb
- module User::PermissionsTrait
- as_trait do
- # methods, validations, etc. regarding contact information go here
- end
- end
-
-== Installation
-
- sudo gem install modularity
-
-
-== Note if you're still on Ruby 1.8.6
-
-Modularity requires Ruby 1.8.7. Earlier versions are missing class_exec. You might be able to hack in class_exec
-using {this}[http://github.com/brynary/rspec/blob/f80d61a399b34f58084a378c85a43a95ff484619/lib/spec/extensions/instance_exec.rb] as a guide, but it's not pretty.
-
-== Credits
-
-Henning Koch
-
-{makandra.com}[http://makandra.com/]
-
-{gem-session.com}[http://gem-session.com/]
diff --git a/Rakefile b/Rakefile
index 8c158fe..666a2e5 100755
--- a/Rakefile
+++ b/Rakefile
@@ -1,12 +1,5 @@
-require 'rake'
-require 'spec/rake/spectask'
require 'bundler/gem_tasks'
+require 'bundler/setup'
+require 'gemika/tasks'
-desc 'Default: Run all specs.'
-task :default => :spec
-
-desc "Run all specs"
-Spec::Rake::SpecTask.new() do |t|
- t.spec_opts = ['--options', "\"spec/spec.opts\""]
- t.spec_files = FileList['spec/**/*_spec.rb']
-end
+task default: 'matrix:spec'
diff --git a/bin/console b/bin/console
new file mode 100755
index 0000000..5f3a90a
--- /dev/null
+++ b/bin/console
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+
+require 'bundler/setup'
+require 'modularity'
+require 'pry'
+
+# You can add fixtures and/or initialization code here to make experimenting
+# with your gem easier. You can also use a different console, if you like.
+Pry.start
diff --git a/lib/modularity.rb b/lib/modularity.rb
index 39e4ef5..402ce75 100755
--- a/lib/modularity.rb
+++ b/lib/modularity.rb
@@ -1,2 +1 @@
-require 'modularity/does'
-
+require 'modularity/as_trait'
diff --git a/lib/modularity/as_trait.rb b/lib/modularity/as_trait.rb
index f26ae95..bbc3f0a 100755
--- a/lib/modularity/as_trait.rb
+++ b/lib/modularity/as_trait.rb
@@ -1,9 +1,52 @@
module Modularity
+
+ class ParametrizedTrait < Module
+
+ def initialize(blank_trait, args, kwargs)
+ @args = args
+ @kwargs = kwargs
+ @macro = blank_trait.instance_variable_get(:@modularity_macro)
+ include(blank_trait)
+ end
+
+ def included(base)
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7')
+ base.class_exec(*@args, &@macro)
+ else
+ base.class_exec(*@args, **@kwargs, &@macro)
+ end
+ end
+
+ end
+
module AsTrait
- def as_trait(&block)
- @trait_macro = block
+
+ def as_trait(¯o)
+
+ @modularity_macro = macro
+
+ def self.included(base)
+ unless base.is_a?(ParametrizedTrait)
+ base.class_exec(&@modularity_macro)
+ end
+
+ end
+
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7')
+ def self.[](*args)
+ blank_trait = self
+ ParametrizedTrait.new(blank_trait, args, {})
+ end
+ else
+ def self.[](*args, **kwargs)
+ blank_trait = self
+ ParametrizedTrait.new(blank_trait, args, kwargs)
+ end
+ end
+
end
+
end
end
-Object.send :include, Modularity::AsTrait
+Module.send(:include, Modularity::AsTrait)
diff --git a/lib/modularity/does.rb b/lib/modularity/does.rb
deleted file mode 100755
index c14b7c2..0000000
--- a/lib/modularity/does.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'modularity/inflector'
-require 'modularity/as_trait'
-
-module Modularity
- module Does
-
- def self.included(base)
- base.extend ClassMethods
- end
-
- module ClassMethods
- def does(trait_name, *args)
- trait_name = "#{Modularity::Inflector.camelize(trait_name.to_s)}Trait"
- trait = Modularity::Inflector.constantize(trait_name)
- macro = trait.instance_variable_get("@trait_macro") or raise "Missing trait directive in #{trait_name}"
- class_exec(*args, ¯o)
- end
- end
-
- end
-end
-
-Object.send :include, Modularity::Does
diff --git a/lib/modularity/inflector.rb b/lib/modularity/inflector.rb
deleted file mode 100755
index 5706eee..0000000
--- a/lib/modularity/inflector.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# These methods are backported from Rails so modularity works with plain Ruby.
-
-module Modularity
- class Inflector
- class << self
-
- # File activesupport/lib/active_support/inflector.rb, line 178
- def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
- if first_letter_in_uppercase
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
- else
- lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
- end
- end
-
-
- if Module.method(:const_get).arity == 1
- # Tries to find a constant with the name specified in the argument string:
- #
- # "Module".constantize # => Module
- # "Test::Unit".constantize # => Test::Unit
- #
- # The name is assumed to be the one of a top-level constant, no matter whether
- # it starts with "::" or not. No lexical context is taken into account:
- #
- # C = 'outside'
- # module M
- # C = 'inside'
- # C # => 'inside'
- # "C".constantize # => 'outside', same as ::C
- # end
- #
- # NameError is raised when the name is not in CamelCase or the constant is
- # unknown.
- def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
- names.shift if names.empty? || names.first.empty?
-
- constant = Object
- names.each do |name|
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
- end
- constant
- end
- else
- def constantize(camel_cased_word) #:nodoc:
- names = camel_cased_word.split('::')
- names.shift if names.empty? || names.first.empty?
-
- constant = Object
- names.each do |name|
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
- end
- constant
- end
- end
-
- end
- end
-end
diff --git a/lib/modularity/version.rb b/lib/modularity/version.rb
index b84bf07..d40bd34 100644
--- a/lib/modularity/version.rb
+++ b/lib/modularity/version.rb
@@ -1,3 +1,3 @@
module Modularity
- VERSION = '0.6.1'
+ VERSION = '3.2.0'
end
diff --git a/modularity.gemspec b/modularity.gemspec
index 89a9fd0..77d626f 100755
--- a/modularity.gemspec
+++ b/modularity.gemspec
@@ -1,22 +1,28 @@
-# -*- encoding: utf-8 -*-
-$:.push File.expand_path("../lib", __FILE__)
+lib = File.expand_path('lib', __dir__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'modularity/version'
-Gem::Specification.new do |s|
- s.name = %q{modularity}
- s.version = Modularity::VERSION
- s.authors = ["Henning Koch"]
- s.email = %q{github@makandra.de}
- s.homepage = %q{http://github.com/makandra/modularity}
- s.summary = %q{Traits and partial classes for Ruby}
- s.description = %q{Traits and partial classes for Ruby}
+Gem::Specification.new do |spec|
+ spec.name = 'modularity'
+ spec.version = Modularity::VERSION
+ spec.required_ruby_version = '>= 2.5.0'
+ spec.authors = ['Henning Koch']
+ spec.email = ['henning.koch@makandra.de']
- s.files = `git ls-files`.split("\n")
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
- s.require_paths = ["lib"]
+ spec.summary = 'Traits and partial classes for Ruby'
+ spec.description = 'Traits and partial classes for Ruby'
+ spec.homepage = 'https://github.com/makandra/modularity'
+ spec.license = 'MIT'
+ spec.metadata = { 'rubygems_mfa_required' => 'true' }
- s.add_development_dependency('rake')
- s.add_development_dependency('rspec', '<2')
+ # Specify which files should be added to the gem when it is released.
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
+ end
+ spec.bindir = 'exe'
+ spec.executables = spec.files.grep(%r(^exe/)) { |f| File.basename(f) }
+ spec.require_paths = ['lib']
+ # Development dependencies are defined in the Gemfile (therefore no `spec.add_development_dependency` directives)
end
diff --git a/spec/as_trait_spec.rb b/spec/as_trait_spec.rb
deleted file mode 100755
index 48383ea..0000000
--- a/spec/as_trait_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-
-module Trait
- as_trait do
- "hi world"
- end
-end
-
-module ParametrizedTrait
- as_trait do |name|
- "hi, #{name}"
- end
-end
-
-describe Modularity::AsTrait do
-
- describe 'as_trait' do
-
- it "should let modules save a proc upon loading" do
- Trait.instance_variable_get("@trait_macro").call.should == "hi world"
- end
-
- it "should let modules save a proc with parameters upon loading" do
- ParametrizedTrait.instance_variable_get("@trait_macro").call("jean").should == "hi, jean"
- end
-
- end
-
-end
diff --git a/spec/does_spec.rb b/spec/does_spec.rb
deleted file mode 100755
index a918e86..0000000
--- a/spec/does_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'spec_helper'
-
-module SomeTrait
- as_trait do
- some_trait_included
- end
-end
-
-module Some
- module ChildTrait
- as_trait do
- some_child_trait_included
- end
- end
-end
-
-module CallMethodTrait
- as_trait do |field|
- send(field)
- end
-end
-
-module VisibilityTrait
- as_trait do
- def public_method_from_trait
- end
- protected
- def protected_method_from_trait
- end
- private
- def private_method_from_trait
- end
- end
-end
-
-module DefineConstantMethodTrait
- as_trait do |name, return_value|
- define_method name do
- return_value
- end
- end
-end
-
-class Doer
-end
-
-describe Modularity::AsTrait do
-
- describe 'does' do
-
- it "should apply the named module" do
- Doer.should_receive(:some_trait_included)
- Doer.class_eval do
- does "some"
- end
- end
-
- it "should apply a namespaced module, using slash-notation like require" do
- Doer.should_receive(:some_child_trait_included)
- Doer.class_eval do
- does "some/child"
- end
- end
-
- it "should class_eval the as_trait proc on the doer" do
- Doer.should_receive(:foo)
- Doer.class_eval do
- does "call_method", :foo
- end
- end
-
- it "should allow the trait to define methods with different visibility" do
- Doer.class_eval do
- does "visibility"
- end
- instance = Doer.new
- instance.public_methods.collect(&:to_s).should include("public_method_from_trait")
- instance.protected_methods.collect(&:to_s).should include("protected_method_from_trait")
- instance.private_methods.collect(&:to_s).should include("private_method_from_trait")
- end
-
- it "should allow the trait to perform metaprogramming acrobatics" do
- Doer.class_eval do
- does "define_constant_method", "some_method", "some_return_value"
- end
- instance = Doer.new
- instance.should respond_to(:some_method)
- instance.some_method.should == "some_return_value"
- end
-
- end
-
-end
diff --git a/spec/modularity/as_trait_spec.rb b/spec/modularity/as_trait_spec.rb
new file mode 100755
index 0000000..d0cca6a
--- /dev/null
+++ b/spec/modularity/as_trait_spec.rb
@@ -0,0 +1,237 @@
+describe Modularity::AsTrait do
+
+ describe '.included' do
+
+ before :each do
+ @doing_class = Class.new
+ end
+
+ describe 'without parameters' do
+
+ it "applies the trait macro of the given module" do
+
+ module DoesSome
+ as_trait do
+ some_trait_included
+ end
+ end
+
+ @doing_class.should_receive(:some_trait_included)
+
+ @doing_class.class_eval do
+ include DoesSome
+ end
+
+ end
+
+ it "applies the trait macro of the given namespaced module" do
+
+ module Some
+ module DoesChild
+ as_trait do
+ some_child_trait_included
+ end
+ end
+ end
+
+ @doing_class.should_receive(:some_child_trait_included)
+
+ @doing_class.class_eval do
+ include Some::DoesChild
+ end
+
+ end
+
+ it "lets a trait define methods with different visibility" do
+
+ module DoesVisibility
+ as_trait do
+ def public_method_from_trait
+ end
+ protected
+ def protected_method_from_trait
+ end
+ private
+ def private_method_from_trait
+ end
+ end
+ end
+
+ @doing_class.class_eval do
+ include DoesVisibility
+ end
+
+ instance = @doing_class.new
+
+ instance.public_methods.collect(&:to_s).should include("public_method_from_trait")
+ instance.protected_methods.collect(&:to_s).should include("protected_method_from_trait")
+ instance.private_methods.collect(&:to_s).should include("private_method_from_trait")
+
+ end
+
+ it 'appends methods outside the trait macro' do
+
+ module HybridModule
+
+ as_trait do
+ define_method :trait_method do
+ end
+ end
+
+ def vanilla_method
+ end
+
+ end
+
+ @doing_class.class_eval do
+ include HybridModule
+ end
+
+ instance = @doing_class.new
+
+ instance.should respond_to(:trait_method)
+ instance.should respond_to(:vanilla_method)
+
+ end
+
+ it 'applies multiple trait macros' do
+
+ module FirstTrait
+ as_trait do
+ define_method :first do
+ end
+ end
+ end
+
+ module SecondTrait
+ as_trait do
+ define_method :second do
+ end
+ end
+ end
+
+ @doing_class.class_eval do
+ include FirstTrait
+ include SecondTrait
+ end
+
+ instance = @doing_class.new
+
+ instance.should respond_to(:first)
+ instance.should respond_to(:second)
+
+ end
+
+ end
+
+ describe "with parameters" do
+
+ it "it applies a trait macro with parameters" do
+
+ module DoesCallMethod
+ as_trait do |field|
+ send(field)
+ end
+ end
+
+ @doing_class.should_receive(:foo)
+ @doing_class.class_eval do
+ include DoesCallMethod[:foo]
+ end
+
+ end
+
+ it "facilitates metaprogramming acrobatics" do
+
+ module DoesDefineConstantMethod
+ as_trait do |name, return_value|
+ define_method name do
+ return_value
+ end
+ end
+ end
+
+ @doing_class.class_eval do
+ include DoesDefineConstantMethod["some_method", "some_return_value"]
+ end
+
+ instance = @doing_class.new
+ instance.should respond_to(:some_method)
+ instance.some_method.should == "some_return_value"
+ end
+
+ it "allies to call an unparametrized trait macro with an empty parameter list" do
+
+ module DoesSome
+ as_trait do
+ some_trait_included
+ end
+ end
+
+ @doing_class.should_receive(:some_trait_included)
+
+ @doing_class.class_eval do
+ include DoesSome[]
+ end
+
+ end
+
+ it 'appends methods outside the trait macro' do
+
+ module HybridModuleWithParameters
+
+ as_trait do |name|
+ define_method name do
+ end
+ end
+
+ def vanilla_method
+ end
+
+ end
+
+ @doing_class.class_eval do
+ include HybridModuleWithParameters[:trait_method]
+ end
+
+ instance = @doing_class.new
+
+ instance.should respond_to(:trait_method)
+ instance.should respond_to(:vanilla_method)
+
+ end
+
+ it 'passes keyword args to the block given to as_trait' do
+
+ module ModuleWithKeywordArgs
+ as_trait do |hash, required_kwarg:, optional_kwarg: 'foo'|
+ define_method :passed_hash do
+ hash
+ end
+
+ define_method :required_keyword do
+ required_kwarg
+ end
+
+ define_method :optional_keyword do
+ optional_kwarg
+ end
+
+ end
+ end
+
+ @doing_class.class_eval do
+ include ModuleWithKeywordArgs[{ first_hash_key: 'value_one', second_hash_key: 'value_two' }, required_kwarg: 'bar']
+ end
+
+ instance = @doing_class.new
+ instance.passed_hash.should eq({ first_hash_key: 'value_one', second_hash_key: 'value_two' })
+ instance.required_keyword.should eq('bar')
+ instance.optional_keyword.should eq('foo')
+ end
+
+ end
+
+ end
+
+end
diff --git a/spec/rcov.opts b/spec/rcov.opts
deleted file mode 100755
index 274ed51..0000000
--- a/spec/rcov.opts
+++ /dev/null
@@ -1,2 +0,0 @@
---exclude "spec/*,gems/*"
---rails
\ No newline at end of file
diff --git a/spec/spec.opts b/spec/spec.opts
deleted file mode 100755
index 391705b..0000000
--- a/spec/spec.opts
+++ /dev/null
@@ -1,4 +0,0 @@
---colour
---format progress
---loadby mtime
---reverse
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9669a3f..b86b309 100755
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,3 +1,6 @@
-$: << File.join(File.dirname(__FILE__), "/../lib" )
+require 'modularity'
-require "#{File.dirname(__FILE__)}/../lib/modularity"
+RSpec.configure do |config|
+ config.expect_with(:rspec) { |expects| expects.syntax = :should }
+ config.mock_with(:rspec) { |mocks| mocks.syntax = [:should, :receive] }
+end