checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
---
2
2
SHA256:
3
- metadata.gz: '03862c5a464a55793074ecb863ac4e7eb4530172923a5f0a8207d8051bf54f96'
4
- data.tar.gz: ffc80ffc2db7d8f71f5598f388fa3cce4efc8399362aa841d3b21cc776508df3
3
+ metadata.gz: b0d06c45078456d085945c12374a78a6e8cd8c5ae83ad7abc7fd55cda175e8c4
4
+ data.tar.gz: 18d5755774da8c851c0a39a25bcfa061445ac1acc06a98e27b22acb896443ab2
5
5
SHA512:
6
- metadata.gz: 2a89e522b5a3fac4be2323be01f8d9e2085e2b54ceee263b1835b75bbae7df2397d68b57c690bcf11f7df1ca5efce2be9add211bcd7a247a8d96e3d3dc1bf7e0
7
- data.tar.gz: df27e76af55099c0031644db8a156124ffa2b72d9ef67ef6a4427119f3c35497979814348bf86cf8ec4a20065834d6dc3839f27647f248eccee2cf2c16812fff
6
+ metadata.gz: '09235347fa19b0da2ea6f20bebdfc4f2048c51760df3889575d7d6cd376a658ccac01a1053b03feaa7f6f89a329ca60db323aa2eb365b62977e29bd05e20a20c'
7
+ data.tar.gz: cff5ba12b81d820189e145f8b15535ec8ab42942963cb31eb501c359f4f2561171326d1cf93adab12df6d1a540f2f2465f4cb18c304316049561237409005656
data/README.md CHANGED
@@ -4,20 +4,20 @@
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, drivers are just a way to put code into a different folder, but there are some rules we like to follow in order to reduce application complexity:
7
+ Technically speaking, drivers are just a fancy name for putting code into a different 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
- Thankfully, we can test that these rules are adhered to by removing drivers before running the test suite.
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.
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 Drivers have one useful property: they can be freely added and removed from your project without breaking anything.
20
+ But Drivers have less friction. They can be freely added and removed from your project without breaking anything. There's no need to mess around with gems, vendoring, or dummy apps.
21
21
22
22
## Usage
23
23
@@ -34,6 +34,21 @@ Just run `rails g driver my_new_driver_name`.
34
34
The `driver` utility technically works with other generators and rake tasks, but is only guaranteed to work with migrations.
35
35
The reason is that some generators assume a particular path, rather than using the Rails path methods.
36
36
37
+ ### Creating a rake task in a driver
38
+
39
+ Every driver includes a `lib/tasks` directory where you can define rake tasks. Rake tasks defined in drivers are automatically loaded and namespaced.
40
+ For example,
41
+
42
+ ```ruby
43
+ # drivers/my_driver/lib/tasks/my_namespace.rake
44
+ namespace :my_namespace do
45
+ task :task_name do
46
+ end
47
+ end
48
+ ```
49
+
50
+ Can be executed using `rake driver:my_driver:my_namespace:task_name`.
51
+
37
52
### Testing for coupling
38
53
39
54
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:
@@ -54,6 +69,11 @@ rake driver:restore
54
69
rake driver:isolate[admin]
55
70
rspec --pattern '{spec,drivers/*/spec}/**{,/*/**}/*_spec.rb'
56
71
rake driver:restore
72
+
73
+ # Short-hand with 'driver' utility!
74
+ bundle exec driver admin do rspec --pattern '{spec,drivers/*/spec}/**{,/*/**}/*_spec.rb'
75
+ # (can run with no drivers as well)
76
+ bundle exec nodriver do rspec --pattern '{spec,drivers/*/spec}/**{,/*/**}/*_spec.rb'
57
77
```
58
78
59
79
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.
@@ -72,23 +92,69 @@ And then execute:
72
92
$ bundle install
73
93
```
74
94
75
- Add this line to your routes.rb
95
+ Add this line to your routes.rb:
76
96
77
97
```ruby
78
98
require 'rails_drivers/routes'
99
+
100
+ # This can go before or after your application's route definitions
101
+ RailsDrivers::Routes.load_driver_routes
79
102
```
80
103
81
- (Optional) Add these lines to your `spec/rails_helper.rb`
104
+ ### RSpec
105
+
106
+ If you use RSpec with FactoryBot, add these lines to your `spec/rails_helper.rb` or `spec/spec_helper.rb`:
82
107
83
108
```ruby
84
109
Dir[Rails.root.join("drivers/*/spec/support/*.rb")].each { |f| require f }
85
110
86
111
RSpec.configure do |config|
112
+ FactoryBot.definition_file_paths += Dir['drivers/*/spec/factories']
113
+ FactoryBot.reload
114
+
87
115
Dir[Rails.root.join('drivers/*/spec')].each { |x| config.project_source_dirs << x }
88
116
Dir[Rails.root.join('drivers/*/lib')].each { |x| config.project_source_dirs << x }
89
117
Dir[Rails.root.join('drivers/*/app')].each { |x| config.project_source_dirs << x }
90
118
end
91
119
```
92
120
121
+ ### Webpacker
122
+
123
+ If you use Webpacker, take a look at this snippet. You'll want to add the code between the comments:
124
+
125
+ ```javascript
126
+ // config/webpack/environment.js
127
+ const { environment } = require('@rails/webpacker')
128
+
129
+ //// Begin driver code ////
130
+ const { config } = require('@rails/webpacker')
131
+ const { sync } = require('glob')
132
+ const { basename, dirname, join, relative, resolve } = require('path')
133
+ const extname = require('path-complete-extname')
134
+
135
+ const getExtensionsGlob = () => {
136
+ const { extensions } = config
137
+ return extensions.length === 1 ? `**/*${extensions[0]}` : `**/*{${extensions.join(',')}}`
138
+ }
139
+
140
+ const addToEntryObject = (sourcePath) => {
141
+ const glob = getExtensionsGlob()
142
+ const rootPath = join(sourcePath, config.source_entry_path)
143
+ const paths = sync(join(rootPath, glob))
144
+ paths.forEach((path) => {
145
+ const namespace = relative(join(rootPath), dirname(path))
146
+ const name = join(namespace, basename(path, extname(path)))
147
+ environment.entry.set(name, resolve(path))
148
+ })
149
+ }
150
+
151
+ sync('drivers/*').forEach((driverPath) => {
152
+ addToEntryObject(join(driverPath, config.source_path));
153
+ })
154
+ //// End driver code ////
155
+
156
+ module.exports = environment
157
+ ```
158
+
93
159
## License
94
160
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/bin/driver CHANGED
@@ -1,15 +1,39 @@
1
1
#!/usr/bin/env ruby
2
2
# frozen_string_literal: true
3
3
4
- APP_PATH = File.expand_path('config/application')
4
+ require 'rails_drivers/files'
5
- require_relative "#{Dir.pwd}/config/boot"
6
5
7
- REPLACE_DEFAULT_PATH_WITH_DRIVER = ARGV.shift
6
+ selected_driver = ARGV.shift
8
7
9
- possible_drivers = Dir['drivers/*'].map { |d| d.split('/').last }
10
- unless possible_drivers.include?(REPLACE_DEFAULT_PATH_WITH_DRIVER)
11
- puts "Unknown driver #{REPLACE_DEFAULT_PATH_WITH_DRIVER}. Must be one of [#{possible_drivers.join(', ')}]"
12
- exit 1
13
- end
8
+ if ARGV[0] == 'do'
9
+ #
10
+ # Run any command with only one driver present
11
+ #
12
+ ARGV.shift
13
+ at_exit { RailsDrivers::Files.restore }
14
+
15
+ if selected_driver == '_clear'
16
+ RailsDrivers::Files.clear
17
+ else
18
+ RailsDrivers::Files.isolate selected_driver
19
+ end
20
+
21
+ Process.wait Process.spawn(*ARGV)
22
+ exit Process.last_status.exitstatus
23
+ else
24
+ #
25
+ # Run 'rails' command as if the driver was the rails app.
26
+ #
27
+ APP_PATH = File.expand_path('config/application')
28
+ REPLACE_DEFAULT_PATH_WITH_DRIVER = selected_driver
14
29
15
- require 'rails/commands'
30
+ require_relative "#{Dir.pwd}/config/boot"
31
+
32
+ possible_drivers = Dir['drivers/*'].map { |d| d.split('/').last }
33
+ unless possible_drivers.include?(REPLACE_DEFAULT_PATH_WITH_DRIVER)
34
+ puts "Unknown driver #{REPLACE_DEFAULT_PATH_WITH_DRIVER}. Must be one of [#{possible_drivers.join(', ')}]"
35
+ exit 1
36
+ end
37
+
38
+ require 'rails/commands'
39
+ end
data/bin/nodriver ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'rails_drivers/files'
5
+
6
+ ARGV.shift if ARGV[0] == 'do'
7
+
8
+ at_exit { RailsDrivers::Files.restore }
9
+ RailsDrivers::Files.clear
10
+ Process.wait Process.spawn(*ARGV)
11
+ exit Process.last_status.exitstatus
data/lib/generators/driver/driver_generator.rb CHANGED
@@ -9,7 +9,7 @@ class DriverGenerator < Rails::Generators::NamedBase
9
9
create_file "drivers/#{file_name}/app/views/#{file_name}/.keep", ''
10
10
create_file "drivers/#{file_name}/spec/.keep", ''
11
11
create_file "drivers/#{file_name}/db/migrate/.keep", ''
12
- create_file "drivers/#{file_name}/lib/.keep", ''
12
+ create_file "drivers/#{file_name}/lib/tasks/.keep", ''
13
13
14
14
template 'routes.rb.erb', "drivers/#{file_name}/config/routes.rb"
15
15
template 'initializer.rb.erb', "drivers/#{file_name}/config/initializers/#{file_name}_feature.rb"
data/lib/rails_drivers.rb CHANGED
@@ -5,11 +5,13 @@ require 'rails_drivers/setup'
5
5
require 'rails_drivers/railtie'
6
6
7
7
module RailsDrivers
8
- def self.loaded
9
- @loaded ||= []
10
- end
8
+ class << self
9
+ def loaded
10
+ @loaded ||= []
11
+ end
11
12
12
- def self.freeze!
13
- @loaded = @loaded&.freeze
13
+ def freeze!
14
+ @loaded = @loaded&.freeze
15
+ end
14
16
end
15
17
end
data/lib/rails_drivers/files.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsDrivers
4
+ module Files
5
+ class Error < StandardError
6
+ end
7
+
8
+ module_function
9
+
10
+ def isolate(driver)
11
+ raise Error, 'No driver specified' if driver.nil? || driver == ''
12
+ raise Error, "Driver #{driver.inspect} not found" unless File.exist?("drivers/#{driver}")
13
+
14
+ FileUtils.mkdir_p 'tmp/drivers'
15
+ Dir['drivers/*'].each do |driver_path|
16
+ next if driver_path.include?("/#{driver}")
17
+
18
+ FileUtils.mv driver_path, "tmp/#{driver_path}"
19
+ end
20
+ end
21
+
22
+ def clear
23
+ FileUtils.mkdir_p 'tmp/drivers'
24
+ Dir['drivers/*'].each do |driver_path|
25
+ FileUtils.mv driver_path, "tmp/#{driver_path}"
26
+ end
27
+ end
28
+
29
+ def restore
30
+ Dir['tmp/drivers/*'].each do |tmp_driver_path|
31
+ driver = tmp_driver_path.split('/').last
32
+ FileUtils.mv tmp_driver_path, "drivers/#{driver}"
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/rails_drivers/railtie.rb CHANGED
@@ -6,6 +6,17 @@ module RailsDrivers
6
6
7
7
rake_tasks do
8
8
load File.expand_path("#{__dir__}/../tasks/rails_drivers_tasks.rake")
9
+
10
+ # load drivers rake tasks
11
+ Dir['drivers/*/lib/tasks/**/*.rake'].each do |driver_rake_file|
12
+ %r{^drivers/(?<driver_name>\w+)/} =~ driver_rake_file
13
+
14
+ namespace(:driver) do
15
+ namespace(driver_name) do
16
+ load driver_rake_file
17
+ end
18
+ end
19
+ end
9
20
end
10
21
11
22
config.before_configuration { setup_paths }
data/lib/rails_drivers/routes.rb CHANGED
@@ -11,6 +11,3 @@ module RailsDrivers
11
11
end
12
12
end
13
13
end
14
-
15
- # This is meant to be executed as soon as the file is required
16
- RailsDrivers::Routes.load_driver_routes
data/lib/rails_drivers/setup.rb CHANGED
@@ -4,6 +4,7 @@ module RailsDrivers
4
4
module Setup
5
5
DRIVER_PATHS = %w[
6
6
app
7
+ app/assets
7
8
app/models
8
9
app/views
9
10
app/controllers
data/lib/rails_drivers/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
# frozen_string_literal: true
2
2
3
3
module RailsDrivers
4
- VERSION = '0.1.0'
4
+ VERSION = '0.4.0'
5
5
end
data/lib/tasks/rails_drivers_tasks.rake CHANGED
@@ -1,46 +1,23 @@
1
1
# frozen_string_literal: true
2
2
3
3
namespace :driver do
4
- DriverError = Class.new(StandardError)
5
-
6
4
desc 'Removes every driver but the one specified. Can be undone with driver:restore.'
7
5
task :isolate, [:driver] do |_t, args|
8
- include FileUtils
9
-
10
- raise DriverError, 'No driver specified' if args.driver.blank?
6
+ require 'rails_drivers/files'
7
+ RailsDrivers::Files.isolate(args.driver)
8
+ rescue RailsDrivers::Files::Error => e
11
- raise DriverError, 'Driver not found' unless File.exist?("drivers/#{args.driver}")
12
-
13
- mkdir_p 'tmp/drivers'
14
- Dir['drivers/*'].each do |driver_path|
15
- next if driver_path.include?("/#{args.driver}")
16
-
17
- mv driver_path, "tmp/#{driver_path}"
18
- puts "Moved #{driver_path} to tmp/drivers/"
19
- end
20
-
21
- rescue DriverError => e
22
9
puts e.message
23
10
end
24
11
25
12
desc 'Removes all drivers. Can be undone with driver:restore.'
26
13
task :clear do
27
- include FileUtils
28
-
14
+ require 'rails_drivers/files'
15
+ RailsDrivers::Files.clear
29
- mkdir_p 'tmp/drivers'
30
- Dir['drivers/*'].each do |driver_path|
31
- mv driver_path, "tmp/#{driver_path}"
32
- puts "Moved #{driver_path} to tmp/drivers/"
33
- end
34
16
end
35
17
36
18
desc 'Undoes the effects of driver:isolate and driver:clear.'
37
19
task :restore do
38
- include FileUtils
39
-
20
+ require 'rails_drivers/files'
21
+ RailsDrivers::Files.restore
40
- Dir['tmp/drivers/*'].each do |tmp_driver_path|
41
- driver = tmp_driver_path.split('/').last
42
- mv tmp_driver_path, "drivers/#{driver}"
43
- puts "Moved #{tmp_driver_path} to drivers/"
44
- end
45
22
end
46
23
end
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.1.0
4
+ version: 0.4.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: 2019-12-12 00:00:00.000000000 Z
11
+ date: 2020-01-14 00:00:00.000000000 Z
12
12
dependencies:
13
13
- !ruby/object:Gem::Dependency
14
14
name: rails
@@ -39,6 +39,20 @@ dependencies:
39
39
- !ruby/object:Gem::Version
40
40
version: '0'
41
41
- !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
42
56
name: sqlite3
43
57
requirement: !ruby/object:Gem::Requirement
44
58
requirements:
@@ -52,12 +66,27 @@ dependencies:
52
66
- - ">="
53
67
- !ruby/object:Gem::Version
54
68
version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webpacker
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.5'
55
83
description: Like Rails Engines, but without the friction. Your Rails app can't access
56
84
them, and they can't access each other.
57
85
email:
58
86
- nbaillie@degica.com
59
87
executables:
60
88
- driver
89
+ - nodriver
61
90
extensions: []
62
91
extra_rdoc_files: []
63
92
files:
@@ -65,12 +94,14 @@ files:
65
94
- README.md
66
95
- Rakefile
67
96
- bin/driver
97
+ - bin/nodriver
68
98
- lib/generators/driver/USAGE
69
99
- lib/generators/driver/driver_generator.rb
70
100
- lib/generators/driver/templates/initializer.rb.erb
71
101
- lib/generators/driver/templates/module.rb.erb
72
102
- lib/generators/driver/templates/routes.rb.erb
73
103
- lib/rails_drivers.rb
104
+ - lib/rails_drivers/files.rb
74
105
- lib/rails_drivers/railtie.rb
75
106
- lib/rails_drivers/routes.rb
76
107
- lib/rails_drivers/setup.rb
@@ -95,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
126
- !ruby/object:Gem::Version
96
127
version: '0'
97
128
requirements: []
98
- rubygems_version: 3.0.6
129
+ rubygems_version: 3.0.3
99
130
signing_key:
100
131
specification_version: 4
101
132
summary: De-coupled separation of concerns for Rails