conventional_extensions 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10071e280679d17703caf2d303edbe063cd04f8899ebc40a990412f23465fe31
4
- data.tar.gz: c2aca1a4cb4536d2b14e454aaca6b26c3c90c70e67b5ed59e4fe533ae9901d74
3
+ metadata.gz: 247b8f8bd45e5773afe0f35f6cf4210321bca59d901637f460e65381e6111711
4
+ data.tar.gz: 6dcf112c67bb3ec3df18ac7ff3b8c7c77b11dc513be3da153c4b825ef8de8775
5
5
  SHA512:
6
- metadata.gz: de54d07c7645e960afad9ea1c23925135fa2884e0af195ec201cba7fdfe4dd66e79c1a7542d6a8db5664e0227c6b3ec3e0f201db2377bb4035610d57a66e5ea1
7
- data.tar.gz: e2f06c3aefedb9d7f0fe0338876d15a4b1030c7f3bf9ab3a683bf3b11ad7c066eed8fbeca57eab5bfb47a0bbaa2c0fbd775a69f4fc44df32077cd0e02f6f928c
6
+ metadata.gz: 0cbb425f5eb691dcaaec2755cdd426e4950d0c26fde845739a23e663939899eb1baeb1e4ebb66f761350ada334eb67b96e0fa929671be246c1016b4a7856f983
7
+ data.tar.gz: 54dc95e8c7bdc679192b1ca5a2bb7e2d9deb38da220036401a9b34ed6d38e48bda24a5e1db80389ab6287d1d788d89e9f97b59fd2829ebcb86ef4fcddbbaee66
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2022-09-07
4
+
5
+ - Fixes `extend ConventionalExtensions.load_on_inherited` not finding the right directory to load extensions from. See https://github.com/kaspth/conventional_extensions/commit/1f6fe9cbf2fe44ed9d699a5dd485eaa366d875a4
6
+
7
+ ## [0.2.0] - 2022-08-27
8
+
9
+ - Replaces the use of `::Kernel.require` with `::Kernel.load` and replaces the `$LOADED_FEATURES` tracking with an internally maintained `Set` for better Zeitwerk (Rails autoloading) inter op. See https://github.com/kaspth/conventional_extensions/pull/2
10
+
3
11
  ## [0.1.0] - 2022-08-27
4
12
 
5
13
  - Initial release
data/README.md CHANGED
@@ -1,17 +1,19 @@
1
1
  # ConventionalExtensions
2
2
 
3
- ConventionalExtensions autoloads extensions to an object based on a file name convention.
3
+ ConventionalExtensions allows splitting up class definitions based on convention, similar to `ActiveSupport::Concern`'s use.
4
+
5
+ The entry point is to call `load_extensions` right after a class is originally defined:
4
6
 
5
7
  ```ruby
6
8
  # lib/post.rb
7
9
  class Post < SomeSuperclass
8
- load_extensions # Will load every Ruby file under `lib/post/extensions`.
10
+ load_extensions # Loads every Ruby file under `lib/post/extensions/*.rb`.
9
11
  end
10
12
  ```
11
13
 
12
14
  ### Defining an extension
13
15
 
14
- Since the loading above happens after the `Post` constant has been defined, we can reopen the class in our extension:
16
+ Since the loading above happens after the `Post` constant has been defined, we can reopen `Post` in an extension:
15
17
 
16
18
  ```ruby
17
19
  # lib/post/extensions/mailroom.rb
@@ -22,11 +24,11 @@ class Post # <- Post is reopened here and so there's no superclass mismatch erro
22
24
  end
23
25
  ```
24
26
 
25
- Now, `Post.new.mailroom` works and `Post.instance_method(:mailroom).source_location` still points to the right file and line.
27
+ Now, `Post.new.mailroom` works and `Post.instance_method(:mailroom).source_location` points to the extension file and line.
26
28
 
27
29
  #### Defining a class method in an extension
28
30
 
29
- Since we're reopening the class we can also define class methods directly:
31
+ Since we're reopening `Post` we can also define class methods directly:
30
32
 
31
33
  ```ruby
32
34
  # lib/post/extensions/cool.rb
@@ -37,9 +39,9 @@ class Post
37
39
  end
38
40
  ```
39
41
 
40
- Now, `Post.cool` works and `Post.method(:cool).source_location` still points to the right file and line.
42
+ Now, `Post.cool` works and `Post.method(:cool).source_location` points to the extension file and line.
41
43
 
42
- Note, any class method macro extensions are available within the top-level Post definition too:
44
+ Note, any class method macro extensions are now available within the top-level `Post` definition too:
43
45
 
44
46
  ```ruby
45
47
  # lib/post.rb
@@ -52,7 +54,7 @@ end
52
54
 
53
55
  ### Skipping class reopening boilerplate
54
56
 
55
- ConventionalExtensions also supports implicit class reopening so you can skip `class Post`, like so:
57
+ ConventionalExtensions also supports implicit class reopening by automatically using `Post.class_eval` so you can skip `class Post`, like so:
56
58
 
57
59
  ```ruby
58
60
  # lib/post/extensions/mailroom.rb
@@ -61,16 +63,16 @@ def mailroom
61
63
  end
62
64
  ```
63
65
 
64
- With this, `Post.new.mailroom` still works and `Post.instance_method(:mailroom).source_location` still points to right file and line.
66
+ With this, `Post.new.mailroom` still works and `Post.instance_method(:mailroom).source_location` points to the extension file and line.
65
67
 
66
- ### Resolve dependencies with a manual `load_extensions`
68
+ ### Resolve dependencies with load hoisting
67
69
 
68
- In case you need to have more fine grained control over the loading, you can call `load_extensions` from within an extension:
70
+ In case you need to have more fine grained control over the loading, you can call `load_extensions` or `load_extension` from within an extension:
69
71
 
70
72
  ```ruby
71
73
  # lib/post/extensions/mailroom.rb
72
74
  load_extension :named
73
- named :sup # We're depending on the class method macro from the `named` extension, and hoisting the loading.
75
+ named :sup # We're depending on the `named` class method macro from the `named` extension, and hoisting the loading.
74
76
 
75
77
  def mailroom
76
78
 
@@ -88,21 +90,21 @@ Whether extensions use explicit or implicit class reopening, `# frozen_string_li
88
90
 
89
91
  ### Providing a base class that expects ConventionalExtensions loading
90
92
 
91
- In case you're planning on setting up a base class, where you're expecting subclasses to use extensions, you can do this:
93
+ In case you're setting up a base class, where you're expecting subclasses to use extensions, you can do:
92
94
 
93
95
  ```ruby
94
96
  class BaseClass
95
- extend ConventionalExtensions.load_on_inherited # This defines the `inherited` method to auto-call `load_extensions`
97
+ extend ConventionalExtensions.load_on_inherited # This calls `load_extensions` automatically in the `inherited` hook.
96
98
  end
97
99
 
98
100
  class Subclass < BaseClass
99
- # No need to write `load_extensions` here
101
+ # No need to write `load_extensions` here, it's called already.
100
102
  end
101
103
  ```
102
104
 
103
105
  ## A less boilerplate heavy alternative to `ActiveSupport::Concern` for Active Records
104
106
 
105
- Typically, when writing an app domain model with `ActiveSupport::Concern` you end defining an object graph like this:
107
+ Typically, when writing an app domain model with `ActiveSupport::Concern` your object graph looks like this:
106
108
 
107
109
  ```ruby
108
110
  # app/models/post.rb
@@ -135,7 +137,7 @@ module Post::Mailroom
135
137
  end
136
138
  ```
137
139
 
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.
140
+ Both `Post::Cool` and `Post::Mailroom` are immediately loaded (via Zeitwerk's file naming conventions) & included. Most often these concern modules are never referred to again, so they're practically implicit modules, yet defined with tricky DSL.
139
141
 
140
142
  With ConventionalExtensions you'd write this instead:
141
143
 
@@ -161,6 +163,10 @@ class Post
161
163
  end
162
164
  ```
163
165
 
166
+ There are places where concerns are more suited:
167
+ * Multi-model concerns in `app/models/concerns`, you'd need modules to help with that.
168
+ * Needing to include multiple levels of modules and have them all inserted directly on the base class, concerns have this built in, but ConventionalExtensions can't support that. It's a rare use case nonetheless.
169
+
164
170
  ## Installation
165
171
 
166
172
  Install the gem and add to the application's Gemfile by executing:
@@ -4,10 +4,8 @@ require "set"
4
4
 
5
5
  class ConventionalExtensions::Loader
6
6
  def initialize(klass, path)
7
- @klass, @name = klass, klass.name
8
- @directory_name = File.join path.chomp(".rb"), "extensions/"
9
-
10
- @loaded = Set.new
7
+ @loaded, @klass, @matcher = Set.new, klass, /\s*class #{klass.name}/
8
+ @path_format = File.join File.dirname(path), underscore(klass.name), "extensions", "%s.rb"
11
9
  end
12
10
 
13
11
  def load(*extensions)
@@ -16,17 +14,23 @@ class ConventionalExtensions::Loader
16
14
  end
17
15
 
18
16
  private
17
+ # Logic borrowed from Active Support:
18
+ # https://github.com/rails/rails/blob/a2fc96a80cf26c11df3e86e86c1b2b61736af80c/activesupport/lib/active_support/inflector/methods.rb#L99
19
+ def underscore(name)
20
+ name.gsub("::", "/").tap { _1.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" } }.tap(&:downcase!)
21
+ end
22
+
19
23
  def extension_paths
20
24
  Dir.glob extension_path_for("*")
21
25
  end
22
26
 
23
27
  def extension_path_for(extension)
24
- @directory_name + "#{extension}.rb"
28
+ @path_format % extension.to_s
25
29
  end
26
30
 
27
31
  def load_one(extension)
28
32
  if @loaded.add?(extension)
29
- if contents = File.read(extension) and contents.match?(/\s*class #{@name}/)
33
+ if contents = File.read(extension) and contents.match?(@matcher)
30
34
  ::Kernel.load extension
31
35
  else
32
36
  @klass.class_eval contents, extension, 0
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConventionalExtensions
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -6,9 +6,7 @@ module ConventionalExtensions
6
6
  Object.extend self # We're enriching object itself, so any object can call `load_extensions`.
7
7
 
8
8
  def load_extensions(*extensions)
9
- loader_defined_before_entrance = defined?(@loader)
10
-
11
- @loader ||= Loader.new(self, caller_locations(1, 1).first.path)
9
+ @loader = Loader.new(self, Object.const_source_location(name).first) unless loader_defined_before_entrance = defined?(@loader)
12
10
  @loader.load(*extensions)
13
11
  ensure
14
12
  @loader = nil unless loader_defined_before_entrance
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conventional_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-28 00:00:00.000000000 Z
11
+ date: 2022-09-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: