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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0d06c45078456d085945c12374a78a6e8cd8c5ae83ad7abc7fd55cda175e8c4
4
- data.tar.gz: 18d5755774da8c851c0a39a25bcfa061445ac1acc06a98e27b22acb896443ab2
3
+ metadata.gz: 52e2b19b5bd5e3ef8921b6c64b08c03c9b1b4a568e70384ca1f855b9c140f783
4
+ data.tar.gz: 4429a66b2b8237e01a91447898b8a8a2a4efb88f5baa93382f594f0e48c7bcde
5
5
  SHA512:
6
- metadata.gz: '09235347fa19b0da2ea6f20bebdfc4f2048c51760df3889575d7d6cd376a658ccac01a1053b03feaa7f6f89a329ca60db323aa2eb365b62977e29bd05e20a20c'
7
- data.tar.gz: cff5ba12b81d820189e145f8b15535ec8ab42942963cb31eb501c359f4f2561171326d1cf93adab12df6d1a540f2f2465f4cb18c304316049561237409005656
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, 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:
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 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.
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
- Just run `rails g driver my_new_driver_name`.
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 assume a particular path, rather than using the Rails path methods.
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
- Add this line to your routes.rb:
144
+ Finally, add these lines to your routes.rb:
96
145
 
97
146
  ```ruby
98
147
  require 'rails_drivers/routes'
@@ -11,5 +11,6 @@ Example:
11
11
  drivers/new_feature/app/models/new_feature
12
12
  drivers/new_feature/app/views/new_feature
13
13
  drivers/new_feature/config
14
+ drivers/new_feature/overrides
14
15
  drivers/new_feature/spec
15
16
  drivers/new_feature/lib
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsDrivers
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/rails_drivers.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'rails_drivers/version'
4
4
  require 'rails_drivers/setup'
5
5
  require 'rails_drivers/railtie'
6
+ require 'rails_drivers/overrides'
6
7
 
7
8
  module RailsDrivers
8
9
  class << self
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.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-14 00:00:00.000000000 Z
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