rails_drivers 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +56 -7
- data/lib/generators/driver/USAGE +1 -0
- data/lib/generators/driver/driver_generator.rb +1 -0
- data/lib/rails_drivers/overrides.rb +46 -0
- data/lib/rails_drivers/version.rb +1 -1
- data/lib/rails_drivers.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52e2b19b5bd5e3ef8921b6c64b08c03c9b1b4a568e70384ca1f855b9c140f783
|
4
|
+
data.tar.gz: 4429a66b2b8237e01a91447898b8a8a2a4efb88f5baa93382f594f0e48c7bcde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1fec26f52e667e09aa237d232dd709b6f4011b48a222762c48b54d5bf19a33145f71d9c6ac3c39d8022b30da32e94042ce25772ed66707af650f91d8aff4b6e
|
7
|
+
data.tar.gz: '08e1af9dde001c22c9ca03bde90c6c10c14975797d6aa0588f08e98dedce155ab9704e9f1ebdb0b7942eced1e8d1b0f67afec230bdbe2458c2e13a23cf0c6803'
|
data/README.md
CHANGED
@@ -4,20 +4,32 @@
|
|
4
4
|
|
5
5
|
Each driver is like a mini Rails app that has full access to the main app. A driver has its own `app`, `config`, `spec`, and `db` folder.
|
6
6
|
|
7
|
-
Technically speaking,
|
7
|
+
Technically speaking, "driver" is just a fancy name for code that live in a different app folder. The advantage of doing this is that it provides clear-cut separation of concerns. If we follow a couple of simple rules, we can actually test that separation:
|
8
8
|
|
9
9
|
- Drivers should not touch other drivers
|
10
10
|
- The main app should not touch drivers directly
|
11
11
|
|
12
12
|
The "main app" refers to the files inside your `<project root>/app` directory.
|
13
13
|
|
14
|
-
If your test suite is good, you can test that these rules are adhered to by selectively adding and removing drivers before running your tests.
|
14
|
+
If your test suite is good enough (see [Testing for coupling](#testing-for-coupling), you can test that these rules are adhered to by selectively adding and removing drivers before running your tests.
|
15
15
|
|
16
16
|
## Aren't these just engines?
|
17
17
|
|
18
18
|
Very similar, yes. They use the same Rails facilities for adding new `app` paths, etc.
|
19
19
|
|
20
|
-
But
|
20
|
+
But practically speaking, drivers come with less friction. They can be freely added and removed from your project without making changes to the main app. There's no need to mess around with gems, routes, or dummy apps.
|
21
|
+
|
22
|
+
Another difference is that drivers have a different dependency direction from engines. Engines are depended on by the Rails app:
|
23
|
+
```
|
24
|
+
depends on
|
25
|
+
(Rails App) --> (Engine)
|
26
|
+
```
|
27
|
+
A Rails app includes an engine as a plugin. The engine doesn't know how the Rails app works. For drivers, it's the other way around:
|
28
|
+
```
|
29
|
+
depends on
|
30
|
+
(Driver) --> (Rails App)
|
31
|
+
```
|
32
|
+
The Rails app doesn't know how its drivers work. It simply acts as a platform for all drivers to be built on. This makes drivers a great way to develop independent features that all rely on the same set of core functionality.
|
21
33
|
|
22
34
|
## Usage
|
23
35
|
|
@@ -25,14 +37,14 @@ Every folder inside `drivers` has its own `app`, `config`, `db`, and `spec` fold
|
|
25
37
|
|
26
38
|
### Creating a new driver
|
27
39
|
|
28
|
-
|
40
|
+
Run `rails g driver my_new_driver_name` to get a scaffold driver.
|
29
41
|
|
30
42
|
### Creating migrations for a driver
|
31
43
|
|
32
44
|
`bundle exec driver my_driver_name generate migration blah etc_etc:string`
|
33
45
|
|
34
46
|
The `driver` utility technically works with other generators and rake tasks, but is only guaranteed to work with migrations.
|
35
|
-
The reason is that some generators
|
47
|
+
The reason is that some generators have hard-coded path strongs, rather than using the Rails path methods.
|
36
48
|
|
37
49
|
### Creating a rake task in a driver
|
38
50
|
|
@@ -49,6 +61,38 @@ end
|
|
49
61
|
|
50
62
|
Can be executed using `rake driver:my_driver:my_namespace:task_name`.
|
51
63
|
|
64
|
+
### Overrides
|
65
|
+
|
66
|
+
Sometimes you want to add a method to a core class, but that method will only be used by one driver. This can be achieved by adding files to your driver's `overrides` directory.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# app/models/product.rb
|
70
|
+
# (doesn't have to be a model - can be anything)
|
71
|
+
class Product < ApplicationRecord
|
72
|
+
# When you include this, every driver's product_override.rb is loaded and
|
73
|
+
# included. Works correctly with autoloading during development.
|
74
|
+
include RailsDrivers::Overrides
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# drivers/my_driver/overrides/product_override.rb
|
79
|
+
module MyDriver
|
80
|
+
module ProductOverride
|
81
|
+
extend ActiveSupport::Concern
|
82
|
+
|
83
|
+
def new_method
|
84
|
+
'Please only call me from code inside my_driver'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Anywhere in my_driver (or elsewhere, but that's bad style)
|
91
|
+
Product.new.new_method
|
92
|
+
```
|
93
|
+
|
94
|
+
For each Override, the accompanying class simply `includes` it, so any methods you define will be available throughout the whole app. To make sure your drivers don't change the core behavior of the app, see [Testing for coupling](#testing-for-coupling).
|
95
|
+
|
52
96
|
### Testing for coupling
|
53
97
|
|
54
98
|
Since drivers are merged into your main application just like engines, there's nothing stopping them from accessing other drivers, and there's nothing stopping your main application from accessing drivers. In order to ensure those things don't happen, we have a handful of rake tasks:
|
@@ -74,11 +118,16 @@ rake driver:restore
|
|
74
118
|
bundle exec driver admin do rspec --pattern '{spec,drivers/*/spec}/**{,/*/**}/*_spec.rb'
|
75
119
|
# (can run with no drivers as well)
|
76
120
|
bundle exec nodriver do rspec --pattern '{spec,drivers/*/spec}/**{,/*/**}/*_spec.rb'
|
121
|
+
|
122
|
+
# Or you can move the driver folders around manually
|
123
|
+
mv drivers/admin tmp/drivers/admin
|
124
|
+
bundle exec rspec --pattern '{spec,drivers/*/spec}/**{,/*/**}/*_spec.rb'
|
125
|
+
mv tmp/drivers/admin drivers/admin
|
77
126
|
```
|
78
127
|
|
79
128
|
This lets you to ensure that the store and admin function properly without each other. Note we're running all of the main app's specs twice. This is good because we also want to make sure the main app is not reaching into drivers.
|
80
129
|
|
81
|
-
Of course there's nothing stopping you from using if-statements to detect whether a driver is present. It's up to you to determine what's a "safe" level of crossover. Generally, if you find yourself using a lot of those if-statements, you should consider rethinking which functionality belongs in a driver and which functionality belongs in your main app.
|
130
|
+
Of course there's nothing stopping you from using if-statements to detect whether a driver is present. It's up to you to determine what's a "safe" level of crossover. Generally, if you find yourself using a lot of those if-statements, you should consider rethinking which functionality belongs in a driver and which functionality belongs in your main app. On the other hand, the if-statements provide clear feature boundaries and can function as feature flags. Turning off a feature is as simple as removing a folder from `drivers`.
|
82
131
|
|
83
132
|
## Installation
|
84
133
|
Add this line to your application's Gemfile:
|
@@ -92,7 +141,7 @@ And then execute:
|
|
92
141
|
$ bundle install
|
93
142
|
```
|
94
143
|
|
95
|
-
|
144
|
+
Finally, add these lines to your routes.rb:
|
96
145
|
|
97
146
|
```ruby
|
98
147
|
require 'rails_drivers/routes'
|
data/lib/generators/driver/USAGE
CHANGED
@@ -10,6 +10,7 @@ class DriverGenerator < Rails::Generators::NamedBase
|
|
10
10
|
create_file "drivers/#{file_name}/spec/.keep", ''
|
11
11
|
create_file "drivers/#{file_name}/db/migrate/.keep", ''
|
12
12
|
create_file "drivers/#{file_name}/lib/tasks/.keep", ''
|
13
|
+
create_file "drivers/#{file_name}/overrides/.keep", ''
|
13
14
|
|
14
15
|
template 'routes.rb.erb', "drivers/#{file_name}/config/routes.rb"
|
15
16
|
template 'initializer.rb.erb', "drivers/#{file_name}/config/initializers/#{file_name}_feature.rb"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsDrivers
|
4
|
+
module Overrides
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Including this module results in all available override modules being
|
8
|
+
# included.
|
9
|
+
included do
|
10
|
+
cattr_reader :driver_overrides
|
11
|
+
|
12
|
+
possible_overrides = Dir.glob(
|
13
|
+
Rails.root.join(
|
14
|
+
'drivers', '*', 'overrides',
|
15
|
+
"#{name.underscore}_override.rb"
|
16
|
+
)
|
17
|
+
)
|
18
|
+
|
19
|
+
@@driver_overrides = possible_overrides.map do |path|
|
20
|
+
require_dependency path
|
21
|
+
%r{drivers/(?<driver_name>[^/]+)/overrides} =~ path
|
22
|
+
|
23
|
+
override = "#{driver_name.classify}::#{name}Override".constantize
|
24
|
+
include override
|
25
|
+
override
|
26
|
+
end.freeze
|
27
|
+
|
28
|
+
singleton_class.prepend CheckForShadowedMethods
|
29
|
+
end
|
30
|
+
|
31
|
+
# This module is prepended to the singleton class of the including class
|
32
|
+
# to detect when an override is attempting to re-define any methods.
|
33
|
+
module CheckForShadowedMethods
|
34
|
+
def method_added(method_name)
|
35
|
+
driver_overrides.each do |override|
|
36
|
+
next unless override.instance_methods.include?(method_name)
|
37
|
+
|
38
|
+
Rails.logger.warn "Driver override method #{override.name}##{method_name} "\
|
39
|
+
"is shadowed by #{name}##{method_name} and will likely not do anything."
|
40
|
+
end
|
41
|
+
|
42
|
+
super(method_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/rails_drivers.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_drivers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nigel Baillie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-01-
|
11
|
+
date: 2020-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -102,6 +102,7 @@ files:
|
|
102
102
|
- lib/generators/driver/templates/routes.rb.erb
|
103
103
|
- lib/rails_drivers.rb
|
104
104
|
- lib/rails_drivers/files.rb
|
105
|
+
- lib/rails_drivers/overrides.rb
|
105
106
|
- lib/rails_drivers/railtie.rb
|
106
107
|
- lib/rails_drivers/routes.rb
|
107
108
|
- lib/rails_drivers/setup.rb
|