Skip to content

Commit

Permalink
Added underscore magic methods as a preferred way to define bang and …
Browse files Browse the repository at this point in the history
…predicate methods.
  • Loading branch information
joelmoss committed Dec 6, 2023
1 parent a790703 commit ede14cb
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 15 deletions.
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,52 @@ In the example above, `namespace` creates and initializes a new class `Twitter`

## Magic bang and predicate methods

If you define a `call` method in your namespaced class, you can then conveniently call that with a bang method:
If you define a `#_!` or `#call` method in your namespaced class, you can then conveniently call that with a bang method:

```ruby
class User
include Spaced

namespace :tweet do
def call(content)
def _!(content)
create_tweet content
end
end
end

user = User.new
user.tweet!('my new tweet') # Will call the `#call` method with whatever arguments you give it.
user.tweet!('my new tweet') # Will call the `#_!` method with whatever arguments you give it.
```

There is also an equivalent `predicate` method:
There is also an equivalent predicate method:

```ruby
namespace :tweet do
def predicate
def _?
false
end
end
user = User.new
user.tweet? # Will call the `#predicate` method.
```

Both bang and predicate methods can be defined using identically named methods:

```ruby
namespace :tweet do
def tweet?
false
end

def tweet!
# ...
end
end
user = User.new
user.tweet? # Will call the `#tweet?` method on `:tweet` namespace.
user.tweet! # Will call the `#tweet!` method on `:tweet` namespace.
```

## Installation

Add this line to your application's Gemfile:
Expand Down
20 changes: 20 additions & 0 deletions fixtures/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,24 @@ def predicate
true
end
end

namespace :sister do
def sister!(append = nil)
"Alex Moss#{append}"
end

def sister?
true
end
end

namespace :mother do
def _!(append = nil)
"Lesley Moss#{append}"
end

def _?
true
end
end
end
34 changes: 26 additions & 8 deletions lib/spaced.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ class Base
end

module ClassMethods
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
def namespace(name, klass = nil, &)
def namespace(name, klass = nil, &) # rubocop:disable Metrics/*
if klass
raise "#{klass} must be a subclass of Spaced::Base" unless klass < Spaced::Base
else
Expand All @@ -41,25 +40,44 @@ def namespace(name, klass = nil, &)
# Define the bang and predicate methods.
methods = klass.instance_methods(false)

if methods.include?(:call)
if methods.include?(:_!)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}!(...); #{name}._!(...); end # def user!; user._!; end
RUBY
elsif methods.include?(:"#{name}!")
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}!(...); #{name}.#{name}!(...); end # def user!; user.user!; end
RUBY
elsif methods.include?(:call) # DEPRECATED
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}!(...); #{name}.call(...); end # def user!(...); user.call(...); end
RUBY
else
define_method :"#{name}!" do
raise NoMethodError, "undefined method `#{name}!' for #<#{klass}>. Have you defined `#{klass}#call`?", caller
raise NoMethodError,
"undefined method `#{name}!' for #<#{klass}>. Have you defined `#{klass}#_!`?", caller
end
end

if methods.include?(:predicate)
define_method(:"#{name}?") { send(name).predicate }
if methods.include?(:_?)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}?; #{name}._?; end # def user?; user._?; end
RUBY
elsif methods.include?(:"#{name}?")
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}?; #{name}.#{name}?; end # def user?; user.user?; end
RUBY
elsif methods.include?(:predicate) # DEPRECATED
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}?; #{name}.predicate; end # def user?; user.predicate; end
RUBY
else
define_method :"#{name}?" do
raise NoMethodError, "undefined method `#{name}?' for #<#{klass}>. Have you defined `#{klass}#predicate`?",
raise NoMethodError,
"undefined method `#{name}?' for #<#{klass}>. Have you defined `#{klass}#_?`?",
caller
end
end
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
end
end
40 changes: 38 additions & 2 deletions test/spaced/namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
expect(user.mum.full_name).to be == "Lesley Moss"
end

with "bang method" do
with "'call' bang method" do
it "aliases to #call" do
expect(user.brother!).to be == "Andy Moss"
end
Expand All @@ -37,9 +37,45 @@
end
end

with "predicate method" do
with "bang named method" do
it "aliases to #sister!" do
expect(user.sister!).to be == "Alex Moss"
end

with "arguments" do
it "passes through arguments" do
expect(user.sister!("?")).to be == "Alex Moss?"
end
end
end

with "bang underscore method" do
it "aliases to #_!" do
expect(user.mother!).to be == "Lesley Moss"
end

with "arguments" do
it "passes through arguments" do
expect(user.mother!("?")).to be == "Lesley Moss?"
end
end
end

with "'predicate' method" do
it "aliases to #predicate" do
expect(user.brother?).to be == true
end
end

with "predicate named method" do
it "aliases to #sister?" do
expect(user.sister?).to be == true
end
end

with "predicate underscore method" do
it "aliases to #_?" do
expect(user.mother?).to be == true
end
end
end

0 comments on commit ede14cb

Please sign in to comment.