drawers 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/lib/drawers.rb +28 -0
- data/lib/drawers/action_view/path_extensions.rb +41 -0
- data/lib/drawers/action_view/resource_resolver.rb +19 -0
- data/lib/drawers/active_support/dependency_extensions.rb +154 -0
- data/lib/drawers/railtie.rb +38 -0
- data/lib/drawers/resource_parts.rb +97 -0
- data/lib/drawers/version.rb +4 -0
- data/lib/tasks/drawers.rake +152 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f965ef04831e18fcc88dbafe26068c72ac7e40cf
|
4
|
+
data.tar.gz: dea8bf6cdd778e52b2c89872cbbf1788ae3da39e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 11d4e29fdb9b5d62addbacc152bf8750c001e616a295a561e46a6067375fc7155a0a3ce76f66991c46255a128140f3d5ac7633d835980735b241d171df83653c
|
7
|
+
data.tar.gz: 94322be4dcd79e7285c20398228dc367edf8dcd26e9122681abb25e34525708533da8c56c51f8ef90fa3d9a576020b7b98f99dfc094654509ce074c8004642aa
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 L. Preston Sego III
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# drawers
|
2
|
+
Group like-classes together. No more silos.
|
3
|
+
|
4
|
+
[](https://badge.fury.io/rb/drawers)
|
5
|
+
[](https://travis-ci.org/NullVoxPopuli/drawers)
|
6
|
+
[](https://codeclimate.com/repos/57dddb2c50dac40e6900197c/feed)
|
7
|
+
[](https://codeclimate.com/repos/57dddb2c50dac40e6900197c/coverage)
|
8
|
+
[](https://gemnasium.com/github.com/NullVoxPopuli/drawers)
|
9
|
+
|
10
|
+
|
11
|
+
## What is this about?
|
12
|
+
|
13
|
+
With large rails application, the default architecture can result in a resource's related files being very spread out through the overall project structure. For example, lets say you have 50 controllers, serializers, policies, and operations. That's _four_ different top level folders that spread out all the related objects. It makes sense do it this way, as it makes rails' autoloading programmatically easy.
|
14
|
+
|
15
|
+
This gem provides a way to re-structure your app so that like-objects are grouped together.
|
16
|
+
|
17
|
+
All this gem does is add some new autoloading / path resolution logic. This gem does not provide any service/operation/policy/etc functionality.
|
18
|
+
|
19
|
+
**All of this is optional, and can be slowly migrated to over time. Adding this gem does not force you to change your app.**
|
20
|
+
|
21
|
+
### The new structure
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
app/
|
25
|
+
├── channels/
|
26
|
+
├── models/
|
27
|
+
│ ├── data/
|
28
|
+
│ │ ├── post.rb
|
29
|
+
│ │ └── comment.rb
|
30
|
+
│ └── graph_data.rb
|
31
|
+
├── jobs/
|
32
|
+
├── mailers/
|
33
|
+
│ └── notification_mailer.rb
|
34
|
+
└── resources/
|
35
|
+
├── posts/
|
36
|
+
│ ├── controller.rb # or posts_controller.rb
|
37
|
+
│ ├── operations.rb # or post_operations.rb
|
38
|
+
│ ├── policy.rb # or post_policy.rb
|
39
|
+
│ └── serializer.rb # or post_serializer.rb
|
40
|
+
└── comments/
|
41
|
+
├── controller.rb
|
42
|
+
├── serializer.rb
|
43
|
+
└── views/
|
44
|
+
├── index.html.erb
|
45
|
+
└── create.html.erb
|
46
|
+
|
47
|
+
```
|
48
|
+
|
49
|
+
Does this new structure mean you have to change the class names of all your classes? Nope. In the above example file structure, `app/resources/posts/controller.rb` _still_ defines `class PostsController < ApplicationController`
|
50
|
+
|
51
|
+
[Checkout the sample rails app in the tests directory.](https://github.com/NullVoxPopuli/drawers/tree/master/spec/support/rails_app/app)
|
52
|
+
|
53
|
+
### The Convention
|
54
|
+
|
55
|
+
Say, for example, you have _any_ class/module defined as:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
module Api # {namespace
|
59
|
+
module V3 # namespace}
|
60
|
+
module UserServices # {resource_name}{resource_type}
|
61
|
+
module Authentication # {class_path
|
62
|
+
class OAuth2 # class_path/file_name}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
As long as some part of the fully qualified class name (in this example: `Api::V3::UserServices::Authentication::OAuth2`) contains any of the [defined keywords](https://github.com/NullVoxPopuli/drawers/blob/master/lib/drawers/active_support/dependency_extensions.rb#L4), the file will be found at `app/resources/api/v3/users/services/authentication/oauth2.rb`.
|
71
|
+
|
72
|
+
The pattern for this is: `app/resources/:namespace/:resource_name/:resource_type/:class_path` where:
|
73
|
+
- `:namespace` is the namespace/parents of the `UserService`
|
74
|
+
- `:resource_type` is a suffix that may be inferred by checking of the inclusion of the defined keywords (linked above)
|
75
|
+
- `:resource_name` is the same module/class as what the `resource_type` is derived from, sans the `resource_type`
|
76
|
+
- `:class_path` is the remaining namespaces and eventually the class that the target file defines.
|
77
|
+
|
78
|
+
So... what if you have a set of classes that don't fit the pattern exactly? You can leave those files where they are currently, or move them to `app/resources`, if it makes sense to do so. Feel free to open an issue / PR if you feel the list of resource types needs updating.
|
79
|
+
|
80
|
+
## Usage
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
gem 'drawers'
|
84
|
+
```
|
85
|
+
|
86
|
+
Including the gem in your gemfile enables the new structure.
|
87
|
+
|
88
|
+
### A note for ActiveModelSerializers
|
89
|
+
|
90
|
+
ActiveModelSerializers, be default, does not consider your _controller's_ namespace when searching for searializers.
|
91
|
+
|
92
|
+
To address that problem, you'll need to add this to the serializer lookup chain
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# config/initializers/active_model_serializers.rb
|
96
|
+
ActiveModelSerializers.config.serializer_lookup_chain.unshift(
|
97
|
+
lambda do |resource_class, _, namespace|
|
98
|
+
"#{namespace.name}::#{resource_class.name}Serializer" if namespace
|
99
|
+
end
|
100
|
+
)
|
101
|
+
```
|
102
|
+
Note: as of 2016-11-04, only [this branch of AMS](https://github.com/rails-api/active_model_serializers/pull/1757) supports a confnigurable lookup chain
|
103
|
+
|
104
|
+
## Migrating
|
105
|
+
|
106
|
+
Each part of your app can be migrated gradually (either manually or automatically).
|
107
|
+
|
108
|
+
In order to automatically migrate resources, just run:
|
109
|
+
|
110
|
+
```bash
|
111
|
+
rake rmu:migrate_resource[Post]
|
112
|
+
```
|
113
|
+
|
114
|
+
This will move all unnamespaced classes that contain any of the [supported resource suffixes](https://github.com/NullVoxPopuli/drawers/blob/master/lib/drawers/active_support_extensions.rb#L4) to the `app/resources/posts` directory.
|
115
|
+
|
116
|
+
## Configuration
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
# (Rails.root)/config/initializers/drawers.rb
|
120
|
+
Drawers.directory = 'pods'
|
121
|
+
```
|
122
|
+
|
123
|
+
Sets the folder for the new structure to be in the `app/pods` directory if you want the new structure separate from the main app files.
|
124
|
+
|
125
|
+
## Contributing
|
126
|
+
|
127
|
+
Feel free to open an issue, or fork and make a pull request.
|
128
|
+
|
129
|
+
All discussion is welcome :-)
|
data/lib/drawers.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module Drawers
|
6
|
+
require 'drawers/active_support/dependency_extensions'
|
7
|
+
require 'drawers/action_view/path_extensions'
|
8
|
+
require 'drawers/action_view/resource_resolver'
|
9
|
+
require 'drawers/resource_parts'
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def directory=(dir)
|
14
|
+
@directory = dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def directory
|
18
|
+
@directory || ''
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'drawers/railtie'
|
22
|
+
ActiveSupport::Dependencies.extend Drawers::DependencyExtensions
|
23
|
+
ActionController::Base.extend Drawers::PathExtensions
|
24
|
+
|
25
|
+
if Rails.version > '5'
|
26
|
+
ActionController::API.extend Drawers::PathExtensions
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Drawers
|
3
|
+
# prepend view paths, setting preferential lookup to the new
|
4
|
+
# RMU folders
|
5
|
+
#
|
6
|
+
# lookup pattern
|
7
|
+
# resources/:namespace/:resource/views/:action/{.:locale,}{.:formats,}{+:variants,}{.:handlers,}
|
8
|
+
# prefix = resources/:namespace/:resource/views/
|
9
|
+
#
|
10
|
+
# default lookup pattern (for reference (as of 5.0.0.1))
|
11
|
+
# :prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}
|
12
|
+
#
|
13
|
+
# This module should only be used as class methods on the inheriting object
|
14
|
+
module PathExtensions
|
15
|
+
def local_prefixes
|
16
|
+
[_rmu_resource_path] + super
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def _rmu_resource_path
|
22
|
+
[
|
23
|
+
_namespace,
|
24
|
+
_resource_name,
|
25
|
+
'views'
|
26
|
+
].flatten.reject(&:blank?).map(&:underscore).join('/')
|
27
|
+
end
|
28
|
+
|
29
|
+
def _resource_name
|
30
|
+
controller_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def _namespace
|
34
|
+
_resource_parts.namespace
|
35
|
+
end
|
36
|
+
|
37
|
+
def _resource_parts
|
38
|
+
@_resource_parts ||= Drawers::ResourceParts.call(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_view'
|
4
|
+
|
5
|
+
module Drawers
|
6
|
+
class ResourceResolver < ::ActionView::OptimizedFileSystemResolver
|
7
|
+
def initialize
|
8
|
+
path = [
|
9
|
+
Rails.root,
|
10
|
+
'app',
|
11
|
+
Drawers.directory,
|
12
|
+
'resources'
|
13
|
+
].reject(&:blank?).join('/')
|
14
|
+
|
15
|
+
super(path)
|
16
|
+
@path = path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Drawers
|
3
|
+
module DependencyExtensions
|
4
|
+
RESOURCE_SUFFIX_NAMES = %w(
|
5
|
+
Controller
|
6
|
+
Serializer
|
7
|
+
Operations
|
8
|
+
Presenters
|
9
|
+
Policy
|
10
|
+
Policies
|
11
|
+
Services
|
12
|
+
).freeze
|
13
|
+
|
14
|
+
# Join all the suffix names together with an "OR" operator
|
15
|
+
RESOURCE_SUFFIXES = /(#{RESOURCE_SUFFIX_NAMES.join('|')})/
|
16
|
+
|
17
|
+
# split on any of the resource suffixes OR the ruby namespace seperator
|
18
|
+
QUALIFIED_NAME_SPLIT = /::|#{RESOURCE_SUFFIXES}/
|
19
|
+
|
20
|
+
def load_from_path(file_path, qualified_name, from_mod, const_name)
|
21
|
+
expanded = File.expand_path(file_path)
|
22
|
+
expanded.sub!(/\.rb\z/, '')
|
23
|
+
|
24
|
+
if loading.include?(expanded)
|
25
|
+
raise "Circular dependency detected while autoloading constant #{qualified_name}"
|
26
|
+
else
|
27
|
+
require_or_load(expanded, qualified_name)
|
28
|
+
unless from_mod.const_defined?(const_name, false)
|
29
|
+
raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it"
|
30
|
+
end
|
31
|
+
|
32
|
+
return from_mod.const_get(const_name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# A look for the possible places that various qualified names could be
|
37
|
+
#
|
38
|
+
# @note The Lookup Rules:
|
39
|
+
# - all resources are plural
|
40
|
+
# - file_names can either be named after the type or traditional ruby/rails nameing
|
41
|
+
# i.e.: posts_controller.rb vs controller.rb
|
42
|
+
# - regular namespacing still applies.
|
43
|
+
# i.e: Api::V2::CategoriesController should be in
|
44
|
+
# api/v2/categories/controller.rb
|
45
|
+
#
|
46
|
+
# @note The Pattern:
|
47
|
+
# - namespace_a - api
|
48
|
+
# - namespace_b - v2
|
49
|
+
# - resource_name (plural) - posts
|
50
|
+
# - file_type.rb - controller.rb (or posts_controller.rb)
|
51
|
+
# - operations.rb (or post_operations.rb)
|
52
|
+
# - folder_type - operations/ (or post_operations/)
|
53
|
+
# - related namespaced classes - create.rb
|
54
|
+
#
|
55
|
+
# All examples assume default resource directory ("resources")
|
56
|
+
# and show the order of lookup
|
57
|
+
#
|
58
|
+
# @example Api::PostsController
|
59
|
+
# Possible Locations
|
60
|
+
# - api/posts/controller.rb
|
61
|
+
# - api/posts/posts_controller.rb
|
62
|
+
#
|
63
|
+
# @example Api::PostSerializer
|
64
|
+
# Possible Locations
|
65
|
+
# - api/posts/serializer.rb
|
66
|
+
# - api/posts/post_serializer.rb
|
67
|
+
#
|
68
|
+
# @example Api::PostOperations::Create
|
69
|
+
# Possible Locations
|
70
|
+
# - api/posts/operations/create.rb
|
71
|
+
# - api/posts/post_operations/create.rb
|
72
|
+
#
|
73
|
+
# @example Api::V2::CategoriesController
|
74
|
+
# Possible Locations
|
75
|
+
# - api/v2/categories/controller.rb
|
76
|
+
# - api/v2/categories/categories_controller.rb
|
77
|
+
#
|
78
|
+
# @param [String] qualified_name fully qualified class/module name to find the file location for
|
79
|
+
def resource_path_from_qualified_name(qualified_name)
|
80
|
+
namespace,
|
81
|
+
resource_name,
|
82
|
+
resource_type, named_resource_type,
|
83
|
+
class_path = ResourceParts.from_name(qualified_name)
|
84
|
+
|
85
|
+
# build all the possible places that this file could be
|
86
|
+
path_options = [
|
87
|
+
|
88
|
+
# api/v2/posts/operations/update
|
89
|
+
to_path(namespace, resource_name, resource_type, class_path),
|
90
|
+
|
91
|
+
# api/v2/posts/post_operations/update
|
92
|
+
to_path(namespace, resource_name, named_resource_type, class_path),
|
93
|
+
|
94
|
+
# api/v2/posts/posts_controller
|
95
|
+
to_path(namespace, resource_name, named_resource_type),
|
96
|
+
|
97
|
+
# api/v2/posts/controller
|
98
|
+
to_path(namespace, resource_name, resource_type)
|
99
|
+
].uniq
|
100
|
+
|
101
|
+
file_path = ''
|
102
|
+
path_options.each do |path_option|
|
103
|
+
file_path = search_for_file(path_option)
|
104
|
+
|
105
|
+
break if file_path.present?
|
106
|
+
end
|
107
|
+
|
108
|
+
file_path
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_path(*args)
|
112
|
+
args.flatten.reject(&:blank?).map(&:underscore).join('/')
|
113
|
+
end
|
114
|
+
|
115
|
+
# Load the constant named +const_name+ which is missing from +from_mod+. If
|
116
|
+
# it is not possible to load the constant into from_mod, try its parent
|
117
|
+
# module using +const_missing+.
|
118
|
+
def load_missing_constant(from_mod, const_name)
|
119
|
+
# always default to the actual implementation
|
120
|
+
super
|
121
|
+
rescue LoadError, NameError => e
|
122
|
+
load_missing_constant_error(from_mod, const_name, e)
|
123
|
+
end
|
124
|
+
|
125
|
+
# the heavy lifting of Drawers is just
|
126
|
+
# adding some additional pathfinding / constat lookup logic
|
127
|
+
# when the default (super) can't find what needs to be found
|
128
|
+
#
|
129
|
+
# @param [Class] from_mod - parent module / class that const_name may be a part of
|
130
|
+
# @param [Symbol] const_name - potential constant to lookup under from_mod
|
131
|
+
# @param [Exception] e - exception from previous error
|
132
|
+
def load_missing_constant_error(from_mod, const_name, e)
|
133
|
+
# examples
|
134
|
+
# - Api::PostsController
|
135
|
+
# - PostsController
|
136
|
+
qualified_name = qualified_name_for(from_mod, const_name)
|
137
|
+
file_path = resource_path_from_qualified_name(qualified_name)
|
138
|
+
|
139
|
+
begin
|
140
|
+
return load_from_path(file_path, qualified_name, from_mod, const_name) if file_path
|
141
|
+
rescue LoadError, NameError => e
|
142
|
+
# Recurse!
|
143
|
+
# not found, check the parent
|
144
|
+
at_the_top = from_mod.parent == from_mod
|
145
|
+
return load_missing_constant_error(from_mod.parent, const_name, e) unless at_the_top
|
146
|
+
raise e
|
147
|
+
end
|
148
|
+
|
149
|
+
name_error = NameError.new(e.message)
|
150
|
+
name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ })
|
151
|
+
raise name_error
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rails/railtie'
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
module Drawers
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
railtie_name :drawers
|
8
|
+
|
9
|
+
rake_tasks do
|
10
|
+
load 'tasks/drawers.rake'
|
11
|
+
end
|
12
|
+
|
13
|
+
# for customizing where the new folder structure is
|
14
|
+
# by default, everything still resides in Rails.root/app
|
15
|
+
config_path = "#{Rails.root}/config/initializers/drawers"
|
16
|
+
config_exists = File.exist?(config_path)
|
17
|
+
require config_path if config_exists
|
18
|
+
|
19
|
+
# add folders to autoload paths
|
20
|
+
initializer 'activeservice.autoload', before: :set_autoload_paths do |app|
|
21
|
+
mu_dir = [
|
22
|
+
Rails.root,
|
23
|
+
'app',
|
24
|
+
Drawers.directory
|
25
|
+
].reject(&:blank?).join('/')
|
26
|
+
|
27
|
+
# New location for ActiveRecord Models
|
28
|
+
app.config.autoload_paths << "#{mu_dir}/models/data"
|
29
|
+
|
30
|
+
# Resources
|
31
|
+
app.config.autoload_paths << "#{mu_dir}/resources/"
|
32
|
+
end
|
33
|
+
|
34
|
+
config.after_initialize do
|
35
|
+
ActionController::Base.prepend_view_path Drawers::ResourceResolver.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Drawers
|
3
|
+
class ResourceParts
|
4
|
+
RESOURCE_SUFFIX_NAMES = Drawers::DependencyExtensions::RESOURCE_SUFFIX_NAMES
|
5
|
+
QUALIFIED_NAME_SPLIT = Drawers::DependencyExtensions::QUALIFIED_NAME_SPLIT
|
6
|
+
|
7
|
+
attr_reader :namespace, :resource_name,
|
8
|
+
:resource_type, :named_resource_type,
|
9
|
+
:class_path
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def from_name(name)
|
13
|
+
resource = call(name)
|
14
|
+
|
15
|
+
[
|
16
|
+
resource.namespace,
|
17
|
+
resource.resource_name,
|
18
|
+
resource.resource_type,
|
19
|
+
resource.named_resource_type,
|
20
|
+
resource.class_path
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(name)
|
25
|
+
resource = new(name)
|
26
|
+
resource.call
|
27
|
+
resource
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(name)
|
32
|
+
@qualified_name = name
|
33
|
+
end
|
34
|
+
|
35
|
+
def call
|
36
|
+
# if this is not part of a resource, don't even bother
|
37
|
+
return unless index_of_resource_type
|
38
|
+
|
39
|
+
# Api, V2, Post, Operations, Update
|
40
|
+
# => Operations
|
41
|
+
@resource_type = qualified_parts[index_of_resource_type]
|
42
|
+
|
43
|
+
# Api, V2, Post, Operations, Update
|
44
|
+
# => Posts
|
45
|
+
#
|
46
|
+
# Posts, Controller
|
47
|
+
# => Posts
|
48
|
+
original_resource_name = qualified_parts[index_of_resource_type - 1]
|
49
|
+
@resource_name = original_resource_name.pluralize
|
50
|
+
|
51
|
+
# Posts_Controller
|
52
|
+
# Post_Operations
|
53
|
+
@named_resource_type = "#{original_resource_name}_#{@resource_type}"
|
54
|
+
|
55
|
+
# Api, V2, Post, Operations, Update
|
56
|
+
# => Api, V2
|
57
|
+
namespace_index = index_of_resource_type - 1
|
58
|
+
@namespace = namespace_index < 1 ? '' : qualified_parts.take(namespace_index)
|
59
|
+
|
60
|
+
# Api, V2, Post, Operations, Update
|
61
|
+
# => Update
|
62
|
+
class_index = index_of_resource_type + 1
|
63
|
+
@class_path = class_index < 1 ? '' : qualified_parts.drop(class_index)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# 1. break apart the qualified name into pieces that can easily be
|
69
|
+
# manipulated
|
70
|
+
#
|
71
|
+
# Api::Posts
|
72
|
+
# => Api, Posts
|
73
|
+
#
|
74
|
+
# Api::PostOperations::Create
|
75
|
+
# => Api, Post, Operations, Create
|
76
|
+
#
|
77
|
+
# Api::PostsController
|
78
|
+
# => Api, Posts, Controller
|
79
|
+
#
|
80
|
+
# Api::V2::PostOperations::Update
|
81
|
+
# => Api, V2, Post, Operations, Update
|
82
|
+
def qualified_parts
|
83
|
+
@qualified_parts ||= @qualified_name.split(QUALIFIED_NAME_SPLIT).reject(&:blank?)
|
84
|
+
end
|
85
|
+
|
86
|
+
# based on the position of of the resource type name,
|
87
|
+
# anything to the left will be the namespace, and anything
|
88
|
+
# to the right will be the file path within the namespace
|
89
|
+
# (may be obvious, but basically, we're 'pivoting' on RESOURCE_SUFFIX_NAMES)
|
90
|
+
#
|
91
|
+
# Given: Api, V2, Post, Operations, Update
|
92
|
+
# ^ index_of_resource_type (3)
|
93
|
+
def index_of_resource_type
|
94
|
+
@index_of_resource_type ||= qualified_parts.index { |x| RESOURCE_SUFFIX_NAMES.include?(x) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
namespace :rmu do
|
3
|
+
config_path = "#{Rails.root}/config/initializers/drawers"
|
4
|
+
config_exists = File.exist?(config_path)
|
5
|
+
require config_path if config_exists
|
6
|
+
|
7
|
+
# @example
|
8
|
+
# rake rmu:migrate_resource Post
|
9
|
+
# @example
|
10
|
+
# rake rmu:migrate_resource Api::Event
|
11
|
+
|
12
|
+
desc 'Moves all files related to a resource to the RMU directory'
|
13
|
+
task :migrate_resource, [:klass_name] => :environment do |_t, args|
|
14
|
+
klass_name = args[:klass_name]
|
15
|
+
|
16
|
+
# Given klass_name,
|
17
|
+
# check known places where related files could be
|
18
|
+
singular = possible_classes(klass_name)
|
19
|
+
plural = possible_classes(klass_name, plural: true)
|
20
|
+
|
21
|
+
possibilities = singular + plural
|
22
|
+
|
23
|
+
possibilities.each do |possible_class|
|
24
|
+
klass = possible_class.safe_constantize
|
25
|
+
|
26
|
+
next unless klass
|
27
|
+
|
28
|
+
location = location_of(klass)
|
29
|
+
|
30
|
+
unless location
|
31
|
+
puts "#{klass.name} could not be found"
|
32
|
+
next
|
33
|
+
end
|
34
|
+
|
35
|
+
next if already_moved?(location)
|
36
|
+
|
37
|
+
destination = destination_for(location)
|
38
|
+
move_file(location, to: destination)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def destination_for(path)
|
43
|
+
# Rails.root does not include 'app/'
|
44
|
+
project_root = Rails.root.to_s
|
45
|
+
relative_path = path.sub(project_root, '')
|
46
|
+
relative_path_parts = relative_path.split('/').reject(&:blank?)
|
47
|
+
|
48
|
+
# remove app dir
|
49
|
+
# ["app", "serializers", "hosted_event_serializer.rb"]
|
50
|
+
# => ["serializers", "hosted_event_serializer.rb"]
|
51
|
+
relative_path_parts.shift
|
52
|
+
|
53
|
+
return if relative_path_parts.length < 2
|
54
|
+
|
55
|
+
# resource type (controller, serializer, etc)
|
56
|
+
# ["serializers", "hosted_event_serializer.rb"]
|
57
|
+
# => serializer
|
58
|
+
resource_type = relative_path_parts.shift.singularize
|
59
|
+
|
60
|
+
# ["hosted_event_serializer.rb"]
|
61
|
+
# => hosted_event_serializer.rb
|
62
|
+
file_name = relative_path_parts.pop
|
63
|
+
|
64
|
+
resource_name, extension = file_name.split("_#{resource_type}")
|
65
|
+
|
66
|
+
namespace = relative_path_parts.join('/')
|
67
|
+
|
68
|
+
destination = [
|
69
|
+
project_root,
|
70
|
+
'app',
|
71
|
+
Drawers.directory,
|
72
|
+
'resources',
|
73
|
+
namespace,
|
74
|
+
resource_name.pluralize,
|
75
|
+
resource_type
|
76
|
+
].reject(&:blank?).join('/')
|
77
|
+
|
78
|
+
destination + extension
|
79
|
+
end
|
80
|
+
|
81
|
+
def move_file(from, to: nil)
|
82
|
+
puts "Moving #{from} to #{to}"
|
83
|
+
return unless to && from
|
84
|
+
|
85
|
+
matches = /.+\/(.+\.\w+)/.match(to)
|
86
|
+
file = matches[1]
|
87
|
+
path = to.sub(file, '')
|
88
|
+
|
89
|
+
unless File.directory?(path)
|
90
|
+
puts 'creating directory...'
|
91
|
+
FileUtils.mkdir_p(path)
|
92
|
+
end
|
93
|
+
|
94
|
+
FileUtils.move(from, to)
|
95
|
+
end
|
96
|
+
|
97
|
+
# See RESOURCE_SUFFIX_NAMES for total options
|
98
|
+
# PostSerializer
|
99
|
+
SINGULAR_RESOURCE_SUFFIXES = %w(
|
100
|
+
Serializer
|
101
|
+
Operations
|
102
|
+
Presenters
|
103
|
+
Policy
|
104
|
+
Policies
|
105
|
+
).freeze
|
106
|
+
|
107
|
+
# PostsController
|
108
|
+
PLURAL_RESOURCE_SUFFIXES = %w(
|
109
|
+
Controller
|
110
|
+
).freeze
|
111
|
+
|
112
|
+
def possible_classes(resource_name, plural: false)
|
113
|
+
klass_name = plural ? resource_name.pluralize : resource_name
|
114
|
+
suffixes = plural ? PLURAL_RESOURCE_SUFFIXES : SINGULAR_RESOURCE_SUFFIXES
|
115
|
+
suffixes.map { |suffix| "#{klass_name}#{suffix}" }
|
116
|
+
end
|
117
|
+
|
118
|
+
def location_of(klass)
|
119
|
+
# Flat default rails structure
|
120
|
+
guessed_path = guess_file_path(klass)
|
121
|
+
return guessed_path if File.exist?(guessed_path)
|
122
|
+
|
123
|
+
# Try to find the file based on method source location
|
124
|
+
# - will not work if a file doesn't define any methods
|
125
|
+
# ( empty sub classes )
|
126
|
+
root = Rails.root.to_s
|
127
|
+
possible_paths = klass.instance_methods(false).map do |m|
|
128
|
+
klass.instance_method(m).source_location.first
|
129
|
+
end.uniq
|
130
|
+
|
131
|
+
possible_paths.select { |path| path.include?(root) }.first
|
132
|
+
end
|
133
|
+
|
134
|
+
def already_moved?(location)
|
135
|
+
location.include?("#{Drawers.directory}/resources")
|
136
|
+
end
|
137
|
+
|
138
|
+
def guess_file_path(klass)
|
139
|
+
underscored = klass.name.underscore
|
140
|
+
file_name = underscored + '.rb'
|
141
|
+
resource_type = underscored.split('_').last
|
142
|
+
|
143
|
+
guessed_path = File.join(
|
144
|
+
Rails.root,
|
145
|
+
'app',
|
146
|
+
resource_type.pluralize,
|
147
|
+
file_name
|
148
|
+
)
|
149
|
+
|
150
|
+
guessed_path
|
151
|
+
end
|
152
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: drawers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- L. Preston Sego III
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
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
|
56
|
+
name: awesome_print
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: factory_girl
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: factory_girl_rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec-rails
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Group like-classes together. No more silos.
|
140
|
+
email: LPSego3+dev@gmail.com
|
141
|
+
executables: []
|
142
|
+
extensions: []
|
143
|
+
extra_rdoc_files: []
|
144
|
+
files:
|
145
|
+
- LICENSE
|
146
|
+
- README.md
|
147
|
+
- lib/drawers.rb
|
148
|
+
- lib/drawers/action_view/path_extensions.rb
|
149
|
+
- lib/drawers/action_view/resource_resolver.rb
|
150
|
+
- lib/drawers/active_support/dependency_extensions.rb
|
151
|
+
- lib/drawers/railtie.rb
|
152
|
+
- lib/drawers/resource_parts.rb
|
153
|
+
- lib/drawers/version.rb
|
154
|
+
- lib/tasks/drawers.rake
|
155
|
+
homepage: https://github.com/NullVoxPopuli/drawers
|
156
|
+
licenses:
|
157
|
+
- MIT
|
158
|
+
metadata: {}
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '2.0'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
requirements: []
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 2.5.1
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: Drawers-0.9.0
|
179
|
+
test_files: []
|