Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename stimpack to packs-rails #51

Merged
merged 4 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
source "https://rubygems.org"

# Specify your gem's dependencies in stimpack.gemspec
# Specify your gem's dependencies in packs-rails.gemspec
gemspec
36 changes: 12 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Stimpack
# packs-rails

`stimpack` establishes and implements a set of conventions for splitting up large monoliths built on top of [`packwerk`](https://github.com/Shopify/packwerk). With `stimpack`, new packages' autoload paths are automatically added to Rails, so your code will immediately become usable and loadable without additional configuration.
`packs-rails` establishes and implements a set of conventions for splitting up large monoliths built on top of the [`packs`](https://github.com/rubyatscale/packs) standard. `packs-rails` makes it easy to use [`packwerk`](https://github.com/Shopify/packwerk) to modularize your rails app. With `packs-rails`, new packages' autoload paths are automatically added to Rails, so your code will immediately become usable and loadable without additional configuration.

Here is an example application that uses `stimpack`:
Here is an example application that uses `packs-rails`:
```
package.yml # root level pack
app/ # Unpackaged code
Expand Down Expand Up @@ -30,7 +30,7 @@ packs/
initializers/ # Initializers can live in packs and load as expected
lib/
tasks/
spec/ # With stimpack, specs for a pack live next to the pack
spec/ # With packs-rails, specs for a pack live next to the pack
public/
my_domain_spec.rb
my_domain/
Expand All @@ -42,7 +42,7 @@ packs/
some_other_non_namespaced_private_model_spec.rb
my_domain/
my_private_namespaced_model_spec.rb
factories/ # Stimpack will automatically load pack factories into FactoryBot
factories/ # packs-rails will automatically load pack factories into FactoryBot
my_domain/
my_private_namespaced_model_factory.rb
my_other_domain/
Expand All @@ -53,26 +53,14 @@ packs/

## Usage

Setting up `stimpack` is straightforward. Simply by including `stimpack` in your `Gemfile` in all environments, `stimpack` will automatically hook into and configure Rails.
Setting up `packs-rails` is straightforward. Simply by including `packs-rails` in your `Gemfile` in all environments, `packs-rails` will automatically hook into and configure Rails.

From there, you can create a `./packs` folder and structure it using the conventions listed above.

If you wish to use a different directory name, eg `components` instead of `packs`, you can customize this in your `config/application.rb` file:

```ruby
# Customize Stimpack's root directory. Note that this has to be done _before_ the Application
# class is declared.
Stimpack.config.root = "components"

module MyCoolApp
class Application < Rails::Application
# ...
end
end
```
If you wish to use a different directory name, eg `components` instead of `packs`, you can customize this by configuring `packs.yml`. See [`packs`](https://github.com/rubyatscale/packs) for more information.

### Splitting routes
`stimpack` allows you to split your application routes for every pack. You just have to create a file describing your routes and then `draw` them in your root `config/routes.rb` file.
`packs-rails` allows you to split your application routes for every pack. You just have to create a file describing your routes and then `draw` them in your root `config/routes.rb` file.

```ruby
# packs/my_domain/config/routes/my_domain.rb
Expand All @@ -95,11 +83,11 @@ metadata:
## Ecosystem and Integrations

### RSpec Integration
Simply add `--require stimpack/rspec` to your `.rspec`.
Simply add `--require packs/rails/rspec` to your `.rspec`.
Or, if you'd like, pass it as an argument to `rspec`:

```
$ rspec --require stimpack/rspec ...
$ rspec --require packs/rails/rspec ...
```

Integration will allow you to run tests as such:
Expand All @@ -125,7 +113,7 @@ rspec packs/foobar/nested_pack

#### parallel_tests

`parallel_tests` has it its own spec discovery mechanism, so Stimpack's RSpec integration doesn't do anything when you use them together.
`parallel_tests` has it its own spec discovery mechanism, so packs-rails's RSpec integration doesn't do anything when you use them together.
To make them work, you'll need to explicitly specify the spec paths:

```bash
Expand All @@ -146,4 +134,4 @@ Setting `KNAPSACK_PRO_TEST_FILE_PATTERN` will tell Knapsack where your specs are

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Gusto/stimpack.
Bug reports and pull requests are welcome on GitHub at https://github.com/rubyatscale/packs-rails.
2 changes: 1 addition & 1 deletion bin/console
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env ruby

require "bundler/setup"
require "stimpack"
require "packs-rails"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
Expand Down
43 changes: 43 additions & 0 deletions lib/packs-rails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'packs'
require "active_support"
require "rails/application"
require 'sorbet-runtime'

module Packs
module Rails
extend ActiveSupport::Autoload

autoload :Integrations
autoload :Railtie
autoload :Stim

class Error < StandardError; end

class << self
attr_reader :config

def root
@root ||= ::Rails::Application.find_root(Dir.pwd)
end
end

@config = ActiveSupport::OrderedOptions.new
@config.paths = %w(
app
app/controllers
app/channels
app/helpers
app/models
app/mailers
app/views
lib
lib/tasks
config
config/locales
config/initializers
config/routes
)
end

require "packs/rails/railtie"
end
11 changes: 11 additions & 0 deletions lib/packs/rails/integrations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "active_support"

module Packs
module Rails
module Integrations
autoload :FactoryBot, "packs/rails/integrations/factory_bot"
autoload :Rails, "packs/rails/integrations/rails"
autoload :RSpec, "packs/rails/integrations/rspec"
end
end
end
17 changes: 17 additions & 0 deletions lib/packs/rails/integrations/factory_bot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# typed: true

module Packs
module Rails
module Integrations
class FactoryBot
def initialize(app)
return unless app.config.respond_to?(:factory_bot)

Packs.all.reject(&:is_gem?).each do |pack|
app.config.factory_bot.definition_file_paths << pack.relative_path.join("spec/factories").to_s
end
end
end
end
end
end
57 changes: 57 additions & 0 deletions lib/packs/rails/integrations/rails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true
# typed: true

require "active_support/inflections"

module Packs
module Rails
module Integrations
class Rails
def initialize(app)
@app = app

Packs::Rails.config.paths.freeze

create_engines
inject_paths
end

def create_engines
Packs.all.reject(&:is_gem?).each do |pack|
next unless pack.metadata['engine']

create_engine(pack)
end
end

def inject_paths
Packs.all.reject(&:is_gem?).each do |pack|
Packs::Rails.config.paths.each do |path|
@app.paths[path] << pack.relative_path.join(path)
end
end
end

private

def create_namespace(name)
namespace = ActiveSupport::Inflector.camelize(name)
namespace.split("::").reduce(Object) do |base, mod|
if base.const_defined?(mod, false)
base.const_get(mod, false)
else
base.const_set(mod, Module.new)
end
end
end

def create_engine(pack)
name = pack.last_name
namespace = create_namespace(name)
stim = Stim.new(pack, namespace)
namespace.const_set("Engine", Class.new(::Rails::Engine)).include(stim)
end
end
end
end
end
60 changes: 60 additions & 0 deletions lib/packs/rails/integrations/rspec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# typed: true

module Packs
module Rails
module Integrations
class RSpec
extend T::Sig

def initialize
# This is the list of directories RSpec was told to run.
to_run = ::RSpec.configuration.instance_variable_get(:@files_or_directories_to_run)
default_path = ::RSpec.configuration.default_path

if to_run == [default_path]
# This is the default case when you run `rspec`. We want to add all the pack's spec paths
# to the collection of directories to run.

pack_paths = Packs.all.map do |pack|
spec_path = pack.relative_path.join(default_path)
spec_path.to_s if spec_path.exist?
end

to_run.concat(pack_paths)
else
# This is when `rspec` is run with a list of directories or files. We scan this list to see
# if any of them matches a pack's directory. If it does, we concat the `default_path` to the
# end of it.
#
# packs/my_pack => packs/my_pack/spec
#
# If it doesn't match a pack path, we leave it alone.

to_run.map! do |path|
if pack = Packs.find(path)
[
pack,
*nested_packs_for(pack)
].map do |pack|
spec_path = pack.relative_path.join(default_path)
spec_path.to_s if spec_path.exist?
end
else
path
end
end
end

::RSpec.configuration.files_or_directories_to_run = to_run.flatten.compact.uniq
end

sig { params(parent_pack: Packs::Pack).returns(T::Array[Packs::Pack]) }
def nested_packs_for(parent_pack)
Packs.all.select do |pack|
pack.name != parent_pack.name && pack.name.include?(parent_pack.name)
end
end
end
end
end
end
16 changes: 16 additions & 0 deletions lib/packs/rails/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require "rails/railtie"

module Packs
module Rails
class Railtie < ::Rails::Railtie
config.before_configuration do |app|
Integrations::Rails.new(app)
Integrations::FactoryBot.new(app)

# This is not used within packs-rails. Rather, this allows OTHER tools to
# hook into packs-rails via ActiveSupport hooks.
ActiveSupport.run_load_hooks(:packs_rails, Packs)
end
end
end
end
3 changes: 3 additions & 0 deletions lib/packs/rails/rspec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require "packs-rails"

Packs::Rails::Integrations::RSpec.new
42 changes: 42 additions & 0 deletions lib/packs/rails/stim.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# typed: true

module Packs
module Rails
class Stim < Module
extend T::Sig

sig { params(pack: Packs::Pack, namespace: Module).void }
def initialize(pack, namespace)
@pack = pack
@namespace = namespace
super()
end

def included(engine)
engine.called_from = @pack.relative_path
engine.extend(ClassMethods)
engine.isolate_namespace(@namespace)

# Set all of these paths to nil because we want the Rails integration to take
# care of them. The purpose of this Engine is really just for the namespace
# isolation.
(Packs::Rails.config.paths +
# In addition to the paths we've delegated to the main app, we don't allow
# Engine Packs to have various capabilities.
%w(
config/environments
db/migrate
)
).uniq.each do |path|
engine.paths[path] = nil
end
end

module ClassMethods
def find_root(_from)
T.unsafe(self).called_from
end
end
end
end
end
5 changes: 5 additions & 0 deletions lib/packs/rails/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Packs
module Rails
VERSION = "0.0.1".freeze
end
end
Loading