drawers 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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 :-)
@@ -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,4 @@
1
+ # frozen_string_literal: true
2
+ module Drawers
3
+ VERSION = '0.9.0'
4
+ 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: []