oh_delegator 0.1.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.
- checksums.yaml +15 -0
- data/Readme.md +90 -0
- data/lib/oh_delegator.rb +8 -0
- data/lib/oh_delegator/base.rb +14 -0
- data/lib/oh_delegator/delegatee.rb +19 -0
- data/lib/oh_delegator/orm.rb +11 -0
- data/lib/oh_delegator/parent_scope.rb +7 -0
- data/lib/oh_delegator/railtie.rb +11 -0
- data/lib/oh_delegator/version.rb +5 -0
- data/oh_delegator.gemspec +18 -0
- data/test/lib/oh_delegator/base_test.rb +28 -0
- data/test/lib/oh_delegator/delegatee_test.rb +33 -0
- data/test/lib/oh_delegator/parent_scope_test.rb +37 -0
- data/test/test_helper.rb +3 -0
- metadata +61 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
!binary "U0hBMQ==":
|
|
3
|
+
metadata.gz: !binary |-
|
|
4
|
+
N2VkNDdlZTA4M2QwMWM5MTM3ZjY2ZmNjMDJkMmNjYjM3ZWQzZTMzZA==
|
|
5
|
+
data.tar.gz: !binary |-
|
|
6
|
+
NWY1ZThhODUxMTQxNTRmZjQzNTdhMWM0Yzc1ZmQ4ODhkNDk2NjMwOA==
|
|
7
|
+
SHA512:
|
|
8
|
+
metadata.gz: !binary |-
|
|
9
|
+
ZjViZWY0OWIxNDdiZWIzZjY5N2MwZGZhY2E1NmYxNjFiZjM0YTkwODEwMGNh
|
|
10
|
+
ZjlhOTZkZjA4NTEzMmEwODE2NWRmMzEzN2Y1N2Q2NzllNGZjNGI1ZmYzZDQw
|
|
11
|
+
NmVmMDRhZjNhZjM2MjUzMTM3NDU3Nzc4MjQ5ZmViNGEyZjIwY2E=
|
|
12
|
+
data.tar.gz: !binary |-
|
|
13
|
+
ZjJiMTM5ZDRkYjQzMTVjYWE4YmMxNmQ1YTljNDk1ODQ4MmVkMzc3MWFiMTNm
|
|
14
|
+
OThhNzE2ZDQyMjRlMTM0MzM2YWVmNWRkNTJmOTRhNmRlODcwMmIwZTk4Y2Mx
|
|
15
|
+
NjAzNmU2N2QzMjMwOTU4MGVjNTE4ODhmM2NhN2E3YjcyMDMyY2Q=
|
data/Readme.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
## Inspiration
|
|
2
|
+
|
|
3
|
+
From [Wikipedia](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself):
|
|
4
|
+
|
|
5
|
+
> The DRY principle is stated as “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
|
|
6
|
+
|
|
7
|
+
## Delegators vs Concerns
|
|
8
|
+
|
|
9
|
+
ActiveSupport::Concern is a convenient solution for splitting ActiveRecord models. It can serve as a single and authoritative feature representation, but it is ambiguous. Consider the following example:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# app/models/concerns/account_profile.rb
|
|
13
|
+
module AccountProfile
|
|
14
|
+
extend ActiveSupport::Concern
|
|
15
|
+
|
|
16
|
+
included do
|
|
17
|
+
has_many :profiles
|
|
18
|
+
|
|
19
|
+
validates :has_profile, inclusion: { in: [true, false] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def active_profile
|
|
23
|
+
profiles.find_by(active: true)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# app/models/account.rb
|
|
28
|
+
class Account < ActiveRecord::Base
|
|
29
|
+
include AccountProfile
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# app/controllers/accounts_controller.rb
|
|
33
|
+
...
|
|
34
|
+
def index
|
|
35
|
+
@profile = @account.active_profile
|
|
36
|
+
end
|
|
37
|
+
...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
When viewing the controller, the location of `@account.active_profile` seems ambiguous. The first thing that most developers would do is look at the `Account` model. Tools like ctags can help, but they have other limitations(e.g. cannot deal with duplicate method names in separate modules).
|
|
41
|
+
|
|
42
|
+
Delegators are more ideal as they comply with all three requirements of DRY. Also, it makes way for editor plugins to accurately track source location. However it lacks any convenient wrapper like `ActiveSupport::Concern` for working with ActiveRecord. This gem serves to fill that gap.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
The delegators can be placed anywhere in your application's load path, the only requirement is that it must be nested under the delegatee object.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# app/delegators/account/profile_delegator.rb
|
|
50
|
+
class Account::ProfileDelegator < OhDelegator::Base
|
|
51
|
+
parent_scope do
|
|
52
|
+
has_many :profiles
|
|
53
|
+
|
|
54
|
+
validates :has_profile, inclusion: { in: [true, false] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def active
|
|
58
|
+
profiles.find_by(active: true)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# app/models/account.rb
|
|
63
|
+
class Account < ActiveRecord::Base
|
|
64
|
+
oh_delegators :profile_delegator
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# app/controllers/accounts_controller.rb
|
|
68
|
+
...
|
|
69
|
+
def index
|
|
70
|
+
@profile = @account.profile_delegator.active
|
|
71
|
+
end
|
|
72
|
+
...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
As we see, migrating code from an **ActiveRecord** model to a delegator is as simple as migrating a concern.
|
|
76
|
+
|
|
77
|
+
## Extras
|
|
78
|
+
|
|
79
|
+
The delegatee object will be available inside the delegator. The name is inferred from the `parent` scope.
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# app/delegators/account/profile_delegator.rb
|
|
83
|
+
class Account::ProfileDelegator < OhDelegator::Base
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
def create_profile
|
|
87
|
+
Profile.create(account: account) # Account::ProfileDelegator.parent.name.downcase == 'account'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
data/lib/oh_delegator.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'delegate'
|
|
2
|
+
|
|
3
|
+
module OhDelegator
|
|
4
|
+
class Base < SimpleDelegator
|
|
5
|
+
extend ParentScope
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def inherited(subclass)
|
|
9
|
+
delegatee_name = subclass.parent.name.downcase
|
|
10
|
+
define_method(delegatee_name, -> { __getobj__ })
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module OhDelegator
|
|
2
|
+
module Delegatee
|
|
3
|
+
def oh_delegators(*attributes)
|
|
4
|
+
attributes.each do |attribute|
|
|
5
|
+
# Read the delegator class alongwith the delegatee.
|
|
6
|
+
klass = "#{ name }::#{ attribute.to_s.classify }".constantize
|
|
7
|
+
|
|
8
|
+
define_method attribute do
|
|
9
|
+
instance_variable_name = "@#{ attribute }"
|
|
10
|
+
instance_variable = instance_variable_get(instance_variable_name)
|
|
11
|
+
|
|
12
|
+
return instance_variable if instance_variable
|
|
13
|
+
|
|
14
|
+
instance_variable_set(instance_variable_name, klass.new(self))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
|
2
|
+
require 'oh_delegator/version'
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.name = 'oh_delegator'
|
|
6
|
+
gem.version = OhDelegator::Version::STRING
|
|
7
|
+
gem.authors = ["BlackDuck Software"]
|
|
8
|
+
gem.email = ["info@blackducksoftware.com"]
|
|
9
|
+
gem.summary = %q{ActiveRecord model delegation}
|
|
10
|
+
gem.description = %q{Use delegators just like concerns}
|
|
11
|
+
gem.homepage = "https://github.com/blackducksw/oh_delegator"
|
|
12
|
+
gem.license = "MIT"
|
|
13
|
+
|
|
14
|
+
gem.files = `git ls-files -z`.split("\x0")
|
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
16
|
+
gem.test_files = gem.files.grep(%r{^test/})
|
|
17
|
+
gem.require_paths = ["lib"]
|
|
18
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
describe OhDelegator::Base do
|
|
4
|
+
before do
|
|
5
|
+
class User
|
|
6
|
+
class ProfileCore < OhDelegator::Base
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class User
|
|
11
|
+
extend OhDelegator::Delegatee
|
|
12
|
+
oh_delegators :profile_core
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
:user
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'must make the delegatee object available to the delegator' do
|
|
21
|
+
user = User.new
|
|
22
|
+
user.profile_core.user.must_equal user
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'must delegate all instance methods to the delegatee' do
|
|
26
|
+
User.new.profile_core.type.must_equal :user
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
describe OhDelegator::Delegatee do
|
|
4
|
+
it 'must add an oh_delegators method when extended' do
|
|
5
|
+
class Person
|
|
6
|
+
extend OhDelegator::Delegatee
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Person.methods.must_include :oh_delegators
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe 'oh_delegators' do
|
|
13
|
+
before do
|
|
14
|
+
class User
|
|
15
|
+
class ProfileCore < OhDelegator::Base
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class User
|
|
20
|
+
extend OhDelegator::Delegatee
|
|
21
|
+
oh_delegators :profile_core
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'must add a accessor for given delegators' do
|
|
26
|
+
User.new.profile_core.must_be :present?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'must make the accessor return a delegator object' do
|
|
30
|
+
User.new.profile_core.class.ancestors.first.must_equal User::ProfileCore
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
describe OhDelegator::ParentScope do
|
|
4
|
+
before do
|
|
5
|
+
class User
|
|
6
|
+
class << self
|
|
7
|
+
attr_reader :class_name
|
|
8
|
+
|
|
9
|
+
def set_class_name
|
|
10
|
+
@class_name = name
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class ProfileCore < OhDelegator::Base
|
|
15
|
+
parent_scope do
|
|
16
|
+
set_class_name
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class User
|
|
22
|
+
extend OhDelegator::Delegatee
|
|
23
|
+
oh_delegators :profile_core
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe 'any method defined in parent_scope' do
|
|
28
|
+
it 'must be called at compile time' do
|
|
29
|
+
User.class_name.must_be :present?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'must be called using delegatee\'s context' do
|
|
33
|
+
User.class_name.must_equal 'User'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
data/test/test_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: oh_delegator
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- BlackDuck Software
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-09-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Use delegators just like concerns
|
|
14
|
+
email:
|
|
15
|
+
- info@blackducksoftware.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- Readme.md
|
|
21
|
+
- lib/oh_delegator.rb
|
|
22
|
+
- lib/oh_delegator/base.rb
|
|
23
|
+
- lib/oh_delegator/delegatee.rb
|
|
24
|
+
- lib/oh_delegator/orm.rb
|
|
25
|
+
- lib/oh_delegator/parent_scope.rb
|
|
26
|
+
- lib/oh_delegator/railtie.rb
|
|
27
|
+
- lib/oh_delegator/version.rb
|
|
28
|
+
- oh_delegator.gemspec
|
|
29
|
+
- test/lib/oh_delegator/base_test.rb
|
|
30
|
+
- test/lib/oh_delegator/delegatee_test.rb
|
|
31
|
+
- test/lib/oh_delegator/parent_scope_test.rb
|
|
32
|
+
- test/test_helper.rb
|
|
33
|
+
homepage: https://github.com/blackducksw/oh_delegator
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata: {}
|
|
37
|
+
post_install_message:
|
|
38
|
+
rdoc_options: []
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ! '>='
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
51
|
+
requirements: []
|
|
52
|
+
rubyforge_project:
|
|
53
|
+
rubygems_version: 2.4.7
|
|
54
|
+
signing_key:
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: ActiveRecord model delegation
|
|
57
|
+
test_files:
|
|
58
|
+
- test/lib/oh_delegator/base_test.rb
|
|
59
|
+
- test/lib/oh_delegator/delegatee_test.rb
|
|
60
|
+
- test/lib/oh_delegator/parent_scope_test.rb
|
|
61
|
+
- test/test_helper.rb
|