strong_concerns 0.0.2 → 0.0.3
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 +19 -15
- data/lib/strong_concerns.rb +51 -26
- data/lib/strong_concerns/intermediate.rb +48 -0
- data/lib/strong_concerns/reflection.rb +44 -0
- data/lib/strong_concerns/version.rb +1 -1
- data/spec/intermediate_spec.rb +40 -0
- data/spec/reflection_spec.rb +35 -0
- data/spec/strong_concerns_spec.rb +43 -30
- metadata +35 -17
- checksums.yaml +0 -7
data/README.md
CHANGED
@@ -8,11 +8,13 @@ But this pattern is easilly can be abused:
|
|
8
8
|
when you just spliting object behavior physicaly, not logicaly, you've got messy code.
|
9
9
|
|
10
10
|
The core point is tracking the internal interface between an object and a concern!
|
11
|
-
Minimal & explicit interface
|
11
|
+
Minimal & explicit interface force you to craft more clean and reusable concerns.
|
12
12
|
The ideal concern is ruby's Enumerable, which require only one method to be implemented in hosting class.
|
13
13
|
|
14
14
|
Gem **strong_concerns** is technically helping you to create concerns in a right way.
|
15
15
|
|
16
|
+
For dependency tracing you should turn on role before usage (DCI inspired)!
|
17
|
+
|
16
18
|
## Installation
|
17
19
|
|
18
20
|
Add this line to your application's Gemfile:
|
@@ -31,11 +33,15 @@ Or install it yourself as:
|
|
31
33
|
|
32
34
|
``` ruby
|
33
35
|
|
34
|
-
module
|
36
|
+
module Old
|
35
37
|
|
36
38
|
# list methods required for concern
|
37
39
|
def self.require_methods
|
38
|
-
%w[
|
40
|
+
%w[bith_date]
|
41
|
+
end
|
42
|
+
|
43
|
+
def age
|
44
|
+
((Date.today - bith_date)/365.0).to_i
|
39
45
|
end
|
40
46
|
|
41
47
|
def young?
|
@@ -61,23 +67,16 @@ module Searchable
|
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
64
|
-
class Person
|
65
|
-
|
66
|
-
attr :age
|
67
|
-
attr :name
|
70
|
+
class Person < Struct.new(:name, :bith_date)
|
68
71
|
|
69
72
|
def self.all
|
70
73
|
[new('nicola', 33), new('ivan', 33)]
|
71
74
|
end
|
72
75
|
|
73
|
-
def initialize( name, age)
|
74
|
-
@name, @age = name, age
|
75
|
-
end
|
76
|
-
|
77
76
|
extend StrongConcerns
|
78
77
|
|
79
|
-
concern
|
80
|
-
exports_methods: %w[young? reproductive?],
|
78
|
+
concern Old,
|
79
|
+
exports_methods: %w[age young? reproductive?],
|
81
80
|
old: 70,
|
82
81
|
young: 14
|
83
82
|
|
@@ -85,12 +84,17 @@ class Person
|
|
85
84
|
exports_methods: %w[find_by_name]
|
86
85
|
end
|
87
86
|
|
88
|
-
nicola = Person.new('nicola',
|
87
|
+
nicola = Person.new('nicola', Date.parse('1980-03-05'))
|
88
|
+
|
89
|
+
nicola.reproductive? #=> raise RoleNotActive error
|
90
|
+
nicola.as(Old)
|
91
|
+
|
89
92
|
if nicola.reproductive?
|
90
93
|
puts "Cool, make me a child!"
|
91
94
|
end
|
92
95
|
|
93
|
-
Person.
|
96
|
+
Person.as(Searchable)
|
97
|
+
.find_by_name('nicola') #=> Person(name: 'nicola')
|
94
98
|
```
|
95
99
|
|
96
100
|
## Contributing
|
data/lib/strong_concerns.rb
CHANGED
@@ -1,50 +1,75 @@
|
|
1
|
-
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__))
|
2
2
|
require "strong_concerns/version"
|
3
3
|
|
4
4
|
module StrongConcerns
|
5
|
-
class
|
6
|
-
extend Forwardable
|
7
|
-
attr_accessor :options
|
5
|
+
class RoleNotActive < StandardError; end
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
autoload :Intermediate, 'strong_concerns/intermediate'
|
8
|
+
autoload :Reflection, 'strong_concerns/reflection'
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def as(mod)
|
12
|
+
role_instance(mod).activate
|
13
|
+
self
|
12
14
|
end
|
13
15
|
|
14
|
-
def
|
15
|
-
|
16
|
+
def role_instances
|
17
|
+
@role_instances ||= {}
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
+
def role_instance(mod)
|
21
|
+
role_instances[mod] ||= self.class
|
22
|
+
.find_instance_role(mod)
|
23
|
+
.instance(self)
|
20
24
|
end
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|
27
|
+
def self.extended(base)
|
28
|
+
base.send(:extend, Reflection)
|
29
|
+
base.send(:extend, ClassMethods)
|
30
|
+
base.send(:include, InstanceMethods)
|
31
|
+
super
|
30
32
|
end
|
31
33
|
|
32
34
|
def concern(mod, options)
|
33
|
-
|
35
|
+
self.add_instance_role(mod, options)
|
34
36
|
options.fetch(:exports_methods).each do |meth|
|
35
37
|
self.send(:define_method, meth) do |*args, &block|
|
36
|
-
|
38
|
+
inter = role_instance(mod)
|
39
|
+
unless inter.active?
|
40
|
+
raise RoleNotActive.new("Your call method <#{meth}> of inactive role <#{mod.name}>!")
|
41
|
+
end
|
42
|
+
inter.send(meth,*args, &block)
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
41
|
-
|
47
|
+
module ClassMethods
|
48
|
+
def as(mod)
|
49
|
+
role_instance(mod).activate
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def role_instances
|
54
|
+
@role_instances ||= {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def role_instance(mod)
|
58
|
+
role_instances[mod] ||= self.find_class_role(mod).instance(self)
|
59
|
+
end
|
60
|
+
end
|
42
61
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
62
|
+
def class_concern(mod, options)
|
63
|
+
self.add_class_role(mod, options)
|
64
|
+
options.fetch(:exports_methods).each do |meth|
|
65
|
+
self.define_singleton_method meth do |*args, &block|
|
66
|
+
inter = role_instance(mod)
|
67
|
+
unless inter.active?
|
68
|
+
raise RoleNotActive.new("Your call method <#{meth}> of inactive role <#{mod.name}>!")
|
69
|
+
end
|
70
|
+
inter.inactivate
|
71
|
+
inter.send(meth,*args, &block)
|
72
|
+
end
|
48
73
|
end
|
49
74
|
end
|
50
75
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
# two way proxy
|
4
|
+
module StrongConcerns
|
5
|
+
class Intermediate
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def self.prepare(mod)
|
9
|
+
Class.new(Intermediate).tap do |kls|
|
10
|
+
kls.send(:include, mod)
|
11
|
+
meths = mod.require_methods
|
12
|
+
kls.def_delegators :@__subject__, *meths
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :options
|
17
|
+
|
18
|
+
def activate
|
19
|
+
@active = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def inactivate
|
23
|
+
@active = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def active?
|
27
|
+
@active
|
28
|
+
end
|
29
|
+
|
30
|
+
def inactive?
|
31
|
+
not active?
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(subject, options)
|
35
|
+
@__subject__ = subject
|
36
|
+
@options = options
|
37
|
+
@active = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(meth)
|
41
|
+
raise NameError.new("Looks like you not list method <#{meth}> in self.require_methods of concern or misspelled it")
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"Intermediate<#{self.methods}>"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module StrongConcerns
|
2
|
+
class Role
|
3
|
+
attr :mod
|
4
|
+
|
5
|
+
def initialize(mod, options)
|
6
|
+
@mod = mod
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def intermediate_class
|
11
|
+
@intermediate_class ||= Intermediate.prepare(@mod)
|
12
|
+
end
|
13
|
+
|
14
|
+
def instance(subject)
|
15
|
+
intermediate_class.new(subject, @options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Reflection
|
20
|
+
def add_class_role(mod, options)
|
21
|
+
self.class_roles[mod] = Role.new(mod, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_instance_role(mod, options)
|
25
|
+
self.instance_roles[mod] = Role.new(mod, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_instance_role(mod)
|
29
|
+
instance_roles[mod]
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_class_role(mod)
|
33
|
+
class_roles[mod]
|
34
|
+
end
|
35
|
+
|
36
|
+
def instance_roles
|
37
|
+
@instance_roles ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def class_roles
|
41
|
+
@class_roles ||= {}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe StrongConcerns::Intermediate do
|
3
|
+
class A
|
4
|
+
def meth
|
5
|
+
"ups"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module B
|
10
|
+
def self.require_methods
|
11
|
+
%w[meth]
|
12
|
+
end
|
13
|
+
|
14
|
+
def decorated_meth
|
15
|
+
"#{meth}#{options[:opt1]}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
subject do
|
20
|
+
obj = A.new
|
21
|
+
kls = described_class.prepare(B)
|
22
|
+
kls.new(obj, opt1: '!!!')
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should turn of/on" do
|
26
|
+
subject.should_not be_active
|
27
|
+
subject.activate
|
28
|
+
subject.should be_active
|
29
|
+
subject.inactivate
|
30
|
+
subject.should be_inactive
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should delegate methods" do
|
34
|
+
subject.meth.should == 'ups'
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should call role methods" do
|
38
|
+
subject.decorated_meth.should == 'ups!!!'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe StrongConcerns::Reflection do
|
3
|
+
class A
|
4
|
+
extend StrongConcerns::Reflection
|
5
|
+
def meth
|
6
|
+
"ups"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module B
|
11
|
+
def self.require_methods
|
12
|
+
%w[meth]
|
13
|
+
end
|
14
|
+
|
15
|
+
def decorated_meth
|
16
|
+
"#{meth}#{options[:opt1]}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
subject { A }
|
21
|
+
|
22
|
+
it "instance roles" do
|
23
|
+
subject.instance_roles.should be_empty
|
24
|
+
subject.add_instance_role(B, opt1: 'val1')
|
25
|
+
role = subject.find_instance_role(B)
|
26
|
+
role.should be_a(StrongConcerns::Role)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "class roles" do
|
30
|
+
subject.class_roles.should be_empty
|
31
|
+
subject.add_class_role(B, opt1: 'val1')
|
32
|
+
role = subject.find_class_role(B)
|
33
|
+
role.should be_a(StrongConcerns::Role)
|
34
|
+
end
|
35
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'date'
|
2
3
|
|
3
4
|
describe StrongConcerns do
|
4
5
|
module FullNamed
|
@@ -11,9 +12,13 @@ describe StrongConcerns do
|
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
module
|
15
|
+
module Old
|
15
16
|
def self.require_methods
|
16
|
-
%w[
|
17
|
+
%w[bith_date]
|
18
|
+
end
|
19
|
+
|
20
|
+
def age
|
21
|
+
((Date.today - bith_date)/365.0).to_i
|
17
22
|
end
|
18
23
|
|
19
24
|
def young?
|
@@ -44,23 +49,10 @@ describe StrongConcerns do
|
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
47
|
-
class Person
|
48
|
-
|
49
|
-
attr :first_name
|
50
|
-
attr :last_name
|
51
|
-
attr :age
|
52
|
-
|
53
|
-
def initialize(first_name, last_name, age)
|
54
|
-
@first_name = first_name
|
55
|
-
@last_name = last_name
|
56
|
-
@age = age
|
57
|
-
end
|
52
|
+
class Person < Struct.new(:first_name, :last_name, :bith_date)
|
58
53
|
|
59
54
|
def self.all
|
60
|
-
[
|
61
|
-
new('nicola','rhyzhikov', 33),
|
62
|
-
new('ivan','ivanov', 33)
|
63
|
-
]
|
55
|
+
[ new('nicola','rhyzhikov', 33), new('ivan','ivanov', 33) ]
|
64
56
|
end
|
65
57
|
|
66
58
|
extend StrongConcerns
|
@@ -68,8 +60,8 @@ describe StrongConcerns do
|
|
68
60
|
class_concern Searchable,
|
69
61
|
exports_methods: %w[find_by_name]
|
70
62
|
|
71
|
-
concern
|
72
|
-
exports_methods: %w[young? reproductive? breaking],
|
63
|
+
concern Old,
|
64
|
+
exports_methods: %w[age young? reproductive? breaking],
|
73
65
|
young: 14, old: 70
|
74
66
|
|
75
67
|
concern FullNamed,
|
@@ -78,18 +70,39 @@ describe StrongConcerns do
|
|
78
70
|
end
|
79
71
|
|
80
72
|
example do
|
81
|
-
Person.new('nicola', 'rhyzhikov',
|
82
|
-
|
83
|
-
|
84
|
-
nicola.
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
73
|
+
nicola = Person.new('nicola', 'rhyzhikov', Date.parse('1980-03-05'))
|
74
|
+
|
75
|
+
-> {
|
76
|
+
nicola.full_name
|
77
|
+
}.should raise_error(StrongConcerns::RoleNotActive)
|
78
|
+
|
79
|
+
nicola.as(FullNamed)
|
80
|
+
|
81
|
+
nicola.full_name.should == "nicola rhyzhikov"
|
82
|
+
|
83
|
+
nicola.as(Old)
|
84
|
+
nicola.should_not be_young
|
85
|
+
nicola.should be_reproductive
|
86
|
+
|
87
|
+
-> {
|
88
|
+
nicola.breaking
|
89
|
+
}.should raise_error(/not list method/)
|
90
90
|
end
|
91
91
|
|
92
|
-
|
93
|
-
|
92
|
+
it "class_concern" do
|
93
|
+
-> {
|
94
|
+
Person
|
95
|
+
.find_by_name('nicola')
|
96
|
+
.should_not be_empty
|
97
|
+
}.should raise_error(StrongConcerns::RoleNotActive)
|
98
|
+
|
99
|
+
Person
|
100
|
+
.as(Searchable)
|
101
|
+
.find_by_name('nicola')
|
102
|
+
.should_not be_empty
|
103
|
+
|
104
|
+
-> {
|
105
|
+
Person.find_by_name('nicola')
|
106
|
+
}.should raise_error(StrongConcerns::RoleNotActive)
|
94
107
|
end
|
95
108
|
end
|
metadata
CHANGED
@@ -1,43 +1,48 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_concerns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.3
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- niquola
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2013-
|
12
|
+
date: 2013-08-06 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
|
-
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
15
|
+
version_requirements: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.3'
|
20
|
-
|
20
|
+
none: false
|
21
21
|
prerelease: false
|
22
|
-
|
22
|
+
name: bundler
|
23
|
+
type: :development
|
24
|
+
requirement: !ruby/object:Gem::Requirement
|
23
25
|
requirements:
|
24
26
|
- - ~>
|
25
27
|
- !ruby/object:Gem::Version
|
26
28
|
version: '1.3'
|
29
|
+
none: false
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
|
-
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
32
|
requirements:
|
31
|
-
- - '>='
|
33
|
+
- - ! '>='
|
32
34
|
- !ruby/object:Gem::Version
|
33
35
|
version: '0'
|
34
|
-
|
36
|
+
none: false
|
35
37
|
prerelease: false
|
36
|
-
|
38
|
+
name: rake
|
39
|
+
type: :development
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
37
41
|
requirements:
|
38
|
-
- - '>='
|
42
|
+
- - ! '>='
|
39
43
|
- !ruby/object:Gem::Version
|
40
44
|
version: '0'
|
45
|
+
none: false
|
41
46
|
description: Gem **strong_concerns** is technically helping you to create concerns
|
42
47
|
in a right way, minimizing and making explicit interface between object and concern.
|
43
48
|
email:
|
@@ -52,35 +57,48 @@ files:
|
|
52
57
|
- README.md
|
53
58
|
- Rakefile
|
54
59
|
- lib/strong_concerns.rb
|
60
|
+
- lib/strong_concerns/intermediate.rb
|
61
|
+
- lib/strong_concerns/reflection.rb
|
55
62
|
- lib/strong_concerns/version.rb
|
63
|
+
- spec/intermediate_spec.rb
|
64
|
+
- spec/reflection_spec.rb
|
56
65
|
- spec/spec_helper.rb
|
57
66
|
- spec/strong_concerns_spec.rb
|
58
67
|
- strong_concerns.gemspec
|
59
68
|
homepage: https://github.com/niquola/strong_concerns
|
60
69
|
licenses:
|
61
70
|
- MIT
|
62
|
-
metadata: {}
|
63
71
|
post_install_message:
|
64
72
|
rdoc_options: []
|
65
73
|
require_paths:
|
66
74
|
- lib
|
67
75
|
required_ruby_version: !ruby/object:Gem::Requirement
|
68
76
|
requirements:
|
69
|
-
- - '>='
|
77
|
+
- - ! '>='
|
70
78
|
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
hash: -2035030651887043314
|
71
82
|
version: '0'
|
83
|
+
none: false
|
72
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
85
|
requirements:
|
74
|
-
- - '>='
|
86
|
+
- - ! '>='
|
75
87
|
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
hash: -2035030651887043314
|
76
91
|
version: '0'
|
92
|
+
none: false
|
77
93
|
requirements: []
|
78
94
|
rubyforge_project:
|
79
|
-
rubygems_version:
|
95
|
+
rubygems_version: 1.8.25
|
80
96
|
signing_key:
|
81
|
-
specification_version:
|
97
|
+
specification_version: 3
|
82
98
|
summary: Gem **strong_concerns** is technically helping you to create concerns in
|
83
99
|
a right way, minimizing and making explicit interface between object and concern.
|
84
100
|
test_files:
|
101
|
+
- spec/intermediate_spec.rb
|
102
|
+
- spec/reflection_spec.rb
|
85
103
|
- spec/spec_helper.rb
|
86
104
|
- spec/strong_concerns_spec.rb
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: 26c6d7992012ae2a5204e3643a55de1bfbf98b43
|
4
|
-
data.tar.gz: b3344d68b073077ff1849f31cb34969bee7ca56d
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 0e406c4ff2eadf7c553fed7a263a6a16e0773a34acf2d55185d8afc1458f32287fc72c7e3f53cfc0ef035b0b5cbdf7e2ecf6600f949d325f7cae5187ce264c02
|
7
|
-
data.tar.gz: 6f172432785133c1e6cc7b3b4029aa04c58825dc73efd1e1b60951f506c58e54a1d2a3e3c11cee54a91a8d28c059eaad768d453faac544fcb621c99532de836a
|