conventional_extensions 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 +7 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +186 -0
- data/Rakefile +12 -0
- data/conventional_extensions.gemspec +30 -0
- data/lib/conventional_extensions/loader.rb +34 -0
- data/lib/conventional_extensions/version.rb +5 -0
- data/lib/conventional_extensions.rb +26 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7ae73a75b482203013f89eb32515cd611d1b198b8cedeba8681032185bf5f51a
|
4
|
+
data.tar.gz: 6952f8616ad0e94edab0822888d02fd881468110d0d0546ba5117ab5ec1a6d27
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9d1afc244d40c11bb184d0bbed288eac9f2f134d64e0a3957b5f7d08b69892914ba32e764c643d89c3612256e90d74888a8b31a00ad6ec48ff62068ed9f5a4c6
|
7
|
+
data.tar.gz: 22b5807a59e13c7a61bc7bebd6b91e7fac3617e2f3dc61d5119435c78a7a47eafc7540f46c00ca0b616f2f0151cce06300112b9df92e489b9481e9b65b874070
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
conventional_extensions (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.16.3)
|
10
|
+
rake (13.0.6)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
arm64-darwin-20
|
14
|
+
x86_64-linux
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
conventional_extensions!
|
18
|
+
minitest (~> 5.0)
|
19
|
+
rake (~> 13.0)
|
20
|
+
|
21
|
+
BUNDLED WITH
|
22
|
+
2.3.21
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Kasper Timm Hansen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# ConventionalExtensions
|
2
|
+
|
3
|
+
ConventionalExtensions autoloads extensions to an object based on a file name convention.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
# lib/post.rb
|
7
|
+
class Post < SomeSuperclass
|
8
|
+
load_extensions # Will load every Ruby file under `lib/post/extensions`.
|
9
|
+
end
|
10
|
+
```
|
11
|
+
|
12
|
+
### Defining an extension
|
13
|
+
|
14
|
+
Since the loading above happens after the `Post` constant has been defined, we can reopen the class in our extension:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# lib/post/extensions/mailroom.rb
|
18
|
+
class Post # <- Post is reopened here and so there's no superclass mismatch error
|
19
|
+
def mailroom
|
20
|
+
puts "you've got mail"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Now, `Post.new.mailroom` works and `Post.instance_method(:mailroom).source_location` still points to the right file and line.
|
26
|
+
|
27
|
+
#### Defining a class method in an extension
|
28
|
+
|
29
|
+
Since we're reopening the class we can also define class methods directly:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# lib/post/extensions/cool.rb
|
33
|
+
class Post
|
34
|
+
def self.cool
|
35
|
+
puts "really cool"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
Now, `Post.cool` works and `Post.method(:cool).source_location` still points to the right file and line.
|
41
|
+
|
42
|
+
Note, any class method macro extensions are available within the top-level Post definition too:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
# lib/post.rb
|
46
|
+
class Post < SomeSuperclass
|
47
|
+
load_extensions # Loads the `cool` extension…
|
48
|
+
|
49
|
+
cool # …and now we can invoke the class method macro.
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
### Skipping class reopening boilerplate
|
54
|
+
|
55
|
+
ConventionalExtensions also supports implicit class reopening so you can skip `class Post`, like so:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# lib/post/extensions/mailroom.rb
|
59
|
+
def mailroom
|
60
|
+
puts "you've got mail"
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
With this, `Post.new.mailroom` still works and `Post.instance_method(:mailroom).source_location` still points to right file and line.
|
65
|
+
|
66
|
+
### Resolve dependencies with a manual `load_extensions`
|
67
|
+
|
68
|
+
In case you need to have more fine grained control over the loading, you can call `load_extensions` from within an extension:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# lib/post/extensions/mailroom.rb
|
72
|
+
load_extension :named
|
73
|
+
named :sup # We're depending on the class method macro from the `named` extension, and hoisting the loading.
|
74
|
+
|
75
|
+
def mailroom
|
76
|
+
…
|
77
|
+
end
|
78
|
+
|
79
|
+
# lib/post/extensions/named.rb
|
80
|
+
def self.named(key)
|
81
|
+
puts key
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
### Supports `# frozen_string_literal: true`
|
86
|
+
|
87
|
+
Whether extensions use explicit or implicit class reopening, `# frozen_string_literal: true` is supported.
|
88
|
+
|
89
|
+
### Providing a base class that expects ConventionalExtensions loading
|
90
|
+
|
91
|
+
In case you're planning on setting up a base class, where you're expecting subclasses to use extensions, you can do this:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class BaseClass
|
95
|
+
extend ConventionalExtensions.load_on_inherited # This defines the `inherited` method to auto-call `load_extensions`
|
96
|
+
end
|
97
|
+
|
98
|
+
class Subclass < BaseClass
|
99
|
+
# No need to write `load_extensions` here
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
## A less boilerplate heavy alternative to `ActiveSupport::Concern` for Active Records
|
104
|
+
|
105
|
+
Typically, when writing an app domain model with `ActiveSupport::Concern` you end defining an object graph like this:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# app/models/post.rb
|
109
|
+
class Post < ApplicationRecord
|
110
|
+
include Cool, Mailroom
|
111
|
+
end
|
112
|
+
|
113
|
+
# app/models/post/cool.rb
|
114
|
+
module Post::Cool
|
115
|
+
extend ActiveSupport::Concern
|
116
|
+
|
117
|
+
class_methods do
|
118
|
+
def cool
|
119
|
+
puts "really cool"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# app/models/post/mailroom.rb
|
125
|
+
module Post::Mailroom
|
126
|
+
extend ActiveSupport::Concern
|
127
|
+
|
128
|
+
included do
|
129
|
+
belongs_to :creator, class_name: "User"
|
130
|
+
end
|
131
|
+
|
132
|
+
def mailroom
|
133
|
+
puts "you've got mail"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
Both the `Post::Cool` and `Post::Mailroom` modules are here immediately loaded (via Zeitwerks file naming conventions) and included. More often than not they're never referred to again, so they're practically implicit modules, yet defined explicitly with a fair bit of DSL on top.
|
139
|
+
|
140
|
+
With ConventionalExtensions you'd write this instead:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# app/models/post.rb
|
144
|
+
class Post < ApplicationRecord # ConventionalExtensions automatically loads extensions for Active Record models.
|
145
|
+
end
|
146
|
+
|
147
|
+
# app/models/post/extensions/cool.rb
|
148
|
+
class Post
|
149
|
+
def self.cool
|
150
|
+
puts "really cool"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# app/models/post/extensions/mailroom.rb
|
155
|
+
class Post
|
156
|
+
belongs_to :creator, class_name: "User"
|
157
|
+
|
158
|
+
def mailroom
|
159
|
+
puts "you've got mail"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
## Installation
|
165
|
+
|
166
|
+
Install the gem and add to the application's Gemfile by executing:
|
167
|
+
|
168
|
+
$ bundle add conventional_extensions
|
169
|
+
|
170
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
171
|
+
|
172
|
+
$ gem install conventional_extensions
|
173
|
+
|
174
|
+
## Development
|
175
|
+
|
176
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
177
|
+
|
178
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
179
|
+
|
180
|
+
## Contributing
|
181
|
+
|
182
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kaspth/conventional_extensions.
|
183
|
+
|
184
|
+
## License
|
185
|
+
|
186
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/conventional_extensions/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "conventional_extensions"
|
7
|
+
spec.version = ConventionalExtensions::VERSION
|
8
|
+
spec.authors = ["Kasper Timm Hansen"]
|
9
|
+
spec.email = ["hey@kaspth.com"]
|
10
|
+
|
11
|
+
spec.summary = "ConventionalExtensions sets up a file naming convention to extend your domain model"
|
12
|
+
spec.homepage = "https://github.com/kaspth/conventional_extensions"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ConventionalExtensions::Loader
|
4
|
+
def initialize(klass, path)
|
5
|
+
@klass, @name = klass, klass.name
|
6
|
+
@directory_name = File.join path.match(/(?=\/extensions|\.rb)/).pre_match, "extensions/"
|
7
|
+
end
|
8
|
+
|
9
|
+
def load(*extensions)
|
10
|
+
paths = extensions.empty? ? extension_paths : extensions.map { extension_path_for _1 }
|
11
|
+
paths.each { load_one _1 }
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def extension_paths
|
16
|
+
Dir.glob extension_path_for("*")
|
17
|
+
end
|
18
|
+
|
19
|
+
def extension_path_for(extension)
|
20
|
+
@directory_name + "#{extension}.rb"
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_one(extension)
|
24
|
+
contents = File.read extension
|
25
|
+
|
26
|
+
case
|
27
|
+
when contents.match?(/\s*class #{@name}/)
|
28
|
+
require extension
|
29
|
+
when !$LOADED_FEATURES.include?(extension)
|
30
|
+
$LOADED_FEATURES << extension
|
31
|
+
@klass.class_eval contents, extension, 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "conventional_extensions/version"
|
4
|
+
|
5
|
+
module ConventionalExtensions
|
6
|
+
Object.extend self # We're enriching object itself, so any object can call `load_extensions`.
|
7
|
+
|
8
|
+
def load_extensions(*extensions)
|
9
|
+
Loader.new(self, caller_locations(1, 1).first.path).load(*extensions)
|
10
|
+
end
|
11
|
+
alias load_extension load_extensions
|
12
|
+
|
13
|
+
# extend ConventionalExtensions.load_on_inherited
|
14
|
+
def self.load_on_inherited() = LoadOnInherited
|
15
|
+
|
16
|
+
module LoadOnInherited
|
17
|
+
def inherited(klass)
|
18
|
+
super
|
19
|
+
klass.load_extensions
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
autoload :Loader, "conventional_extensions/loader"
|
24
|
+
end
|
25
|
+
|
26
|
+
defined?(ActiveSupport.on_load) and ActiveSupport.on_load(:active_record) { extend ConventionalExtensions.load_on_inherited }
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: conventional_extensions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kasper Timm Hansen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- hey@kaspth.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- CHANGELOG.md
|
21
|
+
- Gemfile
|
22
|
+
- Gemfile.lock
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- conventional_extensions.gemspec
|
27
|
+
- lib/conventional_extensions.rb
|
28
|
+
- lib/conventional_extensions/loader.rb
|
29
|
+
- lib/conventional_extensions/version.rb
|
30
|
+
homepage: https://github.com/kaspth/conventional_extensions
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata:
|
34
|
+
homepage_uri: https://github.com/kaspth/conventional_extensions
|
35
|
+
source_code_uri: https://github.com/kaspth/conventional_extensions
|
36
|
+
changelog_uri: https://github.com/kaspth/conventional_extensions/blob/main/CHANGELOG.md
|
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: 3.0.0
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubygems_version: 3.3.21
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: ConventionalExtensions sets up a file naming convention to extend your domain
|
56
|
+
model
|
57
|
+
test_files: []
|