drawers 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/drawers.svg)](https://badge.fury.io/rb/drawers)
|
5
|
+
[![Build Status](https://travis-ci.org/NullVoxPopuli/drawers.svg?branch=master)](https://travis-ci.org/NullVoxPopuli/drawers)
|
6
|
+
[![Code Climate](https://codeclimate.com/repos/57dddb2c50dac40e6900197c/badges/73a0a0761e417c655b68/gpa.svg)](https://codeclimate.com/repos/57dddb2c50dac40e6900197c/feed)
|
7
|
+
[![Test Coverage](https://codeclimate.com/repos/57dddb2c50dac40e6900197c/badges/73a0a0761e417c655b68/coverage.svg)](https://codeclimate.com/repos/57dddb2c50dac40e6900197c/coverage)
|
8
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/NullVoxPopuli/drawers.svg)](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: []
|