rails_module_unification 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ec1fa46bb3f2a6ff70fa6a4752fe649118ab97f
|
4
|
+
data.tar.gz: 62de027845335d0a7cf3e5d497b6b0f470cb340f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a192edd674eec00fd0fe2730616ed35f2d4c51479c6299b3749eed13a670027a55cc596f01ecc31b9645a1885161e9e4837af28cee97a5c0a1bdf03b31645de
|
7
|
+
data.tar.gz: cfe7e1e0987012999ce4309c016a772f4c8f0adb2aaf06c569c964a2f1cee772eb99dd303016c44aee28bcdf73ea67437ef8539f9dd34ada1b51d19c927e71ed
|
data/README.md
CHANGED
@@ -15,7 +15,6 @@ This gem provides a way to re-structure your app so that like-objects are groupe
|
|
15
15
|
|
16
16
|
### The new structure
|
17
17
|
|
18
|
-
|
19
18
|
```
|
20
19
|
app/
|
21
20
|
├── channels/
|
@@ -38,6 +37,8 @@ app/
|
|
38
37
|
|
39
38
|
```
|
40
39
|
|
40
|
+
[Checkout the sample rails app in the tests directory.](https://github.com/NullVoxPopuli/rails_module_unification/tree/master/spec/support/rails_app/app)
|
41
|
+
|
41
42
|
## Usage
|
42
43
|
|
43
44
|
```ruby
|
@@ -46,13 +47,26 @@ gem 'rails_module_unification'
|
|
46
47
|
|
47
48
|
Including the gem in your gemfile enables the new structure.
|
48
49
|
|
50
|
+
## Migrating
|
51
|
+
|
52
|
+
Each part of your app can be migrated gradually (either manually or automatically).
|
53
|
+
|
54
|
+
In order to automatically migrate resources, just run:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
rake rmu:migrate_resource[Post]
|
58
|
+
```
|
59
|
+
|
60
|
+
This will move all unnamespaced classes that contain any of the [supported resource suffixes](https://github.com/NullVoxPopuli/rails_module_unification/blob/master/lib/rails_module_unification/active_support_extensions.rb#L4) to the `app/resources/posts` directory.
|
61
|
+
|
49
62
|
## Configuration
|
50
63
|
|
51
64
|
```ruby
|
65
|
+
# (Rails.root)/config/initializers/rails_module_unification.rb
|
52
66
|
RailsModuleUnification.directory = 'pods'
|
53
67
|
```
|
54
68
|
|
55
|
-
Sets the folder for the new structure to be in the `app/pods` directory
|
69
|
+
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.
|
56
70
|
|
57
71
|
## Contributing
|
58
72
|
|
@@ -1,7 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module RailsModuleUnification
|
3
3
|
module ActiveSupportExtensions
|
4
|
-
|
4
|
+
RESOURCE_SUFFIX_NAMES = %w(
|
5
|
+
Controller
|
6
|
+
Serializer
|
7
|
+
Operations
|
8
|
+
Presenters
|
9
|
+
Policy
|
10
|
+
Policies
|
11
|
+
).freeze
|
12
|
+
|
13
|
+
# Join all the suffix names together with an "OR" operator
|
14
|
+
RESOURCE_SUFFIXES = /(#{RESOURCE_SUFFIX_NAMES.join('|')})/
|
15
|
+
|
16
|
+
# split on any of the resource suffixes OR the ruby namespace seperator
|
17
|
+
QUALIFIED_NAME_SPLIT = /::|#{RESOURCE_SUFFIXES}/
|
5
18
|
|
6
19
|
def load_from_path(file_path, qualified_name, from_mod, const_name)
|
7
20
|
expanded = File.expand_path(file_path)
|
@@ -11,107 +24,179 @@ module RailsModuleUnification
|
|
11
24
|
raise "Circular dependency detected while autoloading constant #{qualified_name}"
|
12
25
|
else
|
13
26
|
require_or_load(expanded, qualified_name)
|
14
|
-
|
27
|
+
unless from_mod.const_defined?(const_name, false)
|
28
|
+
raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it"
|
29
|
+
end
|
30
|
+
|
15
31
|
return from_mod.const_get(const_name)
|
16
32
|
end
|
17
33
|
end
|
18
34
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
# A look for the possible places that various qualified names could be
|
36
|
+
#
|
37
|
+
# @note The Lookup Rules:
|
38
|
+
# - all resources are plural
|
39
|
+
# - file_names can either be named after the type or traditional ruby/rails nameing
|
40
|
+
# i.e.: posts_controller.rb vs controller.rb
|
41
|
+
# - regular namespacing still applies.
|
42
|
+
# i.e: Api::V2::CategoriesController should be in
|
43
|
+
# api/v2/categories/controller.rb
|
44
|
+
#
|
45
|
+
# @note The Pattern:
|
46
|
+
# - namespace_a - api
|
47
|
+
# - namespace_b - v2
|
48
|
+
# - resource_name (plural) - posts
|
49
|
+
# - file_type.rb - controller.rb (or posts_controller.rb)
|
50
|
+
# - operations.rb (or post_operations.rb)
|
51
|
+
# - folder_type - operations/ (or post_operations/)
|
52
|
+
# - related namespaced classes - create.rb
|
53
|
+
#
|
54
|
+
# All examples assume default resource directory ("resources")
|
55
|
+
# and show the order of lookup
|
56
|
+
#
|
57
|
+
# @example Api::PostsController
|
58
|
+
# Possible Locations
|
59
|
+
# - api/posts/controller.rb
|
60
|
+
# - api/posts/posts_controller.rb
|
61
|
+
#
|
62
|
+
# @example Api::PostSerializer
|
63
|
+
# Possible Locations
|
64
|
+
# - api/posts/serializer.rb
|
65
|
+
# - api/posts/post_serializer.rb
|
66
|
+
#
|
67
|
+
# @example Api::PostOperations::Create
|
68
|
+
# Possible Locations
|
69
|
+
# - api/posts/operations/create.rb
|
70
|
+
# - api/posts/post_operations/create.rb
|
71
|
+
#
|
72
|
+
# @example Api::V2::CategoriesController
|
73
|
+
# Possible Locations
|
74
|
+
# - api/v2/categories/controller.rb
|
75
|
+
# - api/v2/categories/categories_controller.rb
|
76
|
+
#
|
77
|
+
# @param [String] qualified_name fully qualified class/module name to find the file location for
|
78
|
+
def resource_path_from_qualified_name(qualified_name)
|
79
|
+
# 1. break apart the qualified name into pieces that can easily be
|
80
|
+
# manipulated
|
36
81
|
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# end
|
82
|
+
# Api::Posts
|
83
|
+
# => Api, Posts
|
40
84
|
#
|
41
|
-
#
|
85
|
+
# Api::PostOperations::Create
|
86
|
+
# => Api, Post, Operations, Create
|
42
87
|
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
# Module.nesting # => [S::T, M::N]
|
46
|
-
# end
|
47
|
-
# end
|
88
|
+
# Api::PostsController
|
89
|
+
# => Api, Posts, Controller
|
48
90
|
#
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
91
|
+
# Api::V2::PostOperations::Update
|
92
|
+
# => Api, V2, Post, Operations, Update
|
93
|
+
qualified_parts = qualified_name.split(QUALIFIED_NAME_SPLIT).reject(&:blank?)
|
94
|
+
|
95
|
+
# based on the position of of the resource type name,
|
96
|
+
# anything to the left will be the namespace, and anything
|
97
|
+
# to the right will be the file path within the namespace
|
98
|
+
# (may be obvious, but basically, we're 'pivoting' on RESOURCE_SUFFIX_NAMES)
|
99
|
+
#
|
100
|
+
# Given: Api, V2, Post, Operations, Update
|
101
|
+
# ^ index_of_resource_type (3)
|
102
|
+
index_of_resource_type = qualified_parts.index { |x| RESOURCE_SUFFIX_NAMES.include?(x) }
|
54
103
|
|
55
|
-
|
56
|
-
|
57
|
-
# - api/posts_controller
|
58
|
-
# - posts_controller
|
59
|
-
file_name = qualified_name.underscore.split('/').last
|
104
|
+
# if this is not part of a resource, don't even bother
|
105
|
+
return unless index_of_resource_type
|
60
106
|
|
61
|
-
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
107
|
+
# Api, V2, Post, Operations, Update
|
108
|
+
# => Operations
|
109
|
+
resource_type = qualified_parts[index_of_resource_type]
|
110
|
+
|
111
|
+
# Api, V2, Post, Operations, Update
|
112
|
+
# => Posts
|
113
|
+
#
|
114
|
+
# Posts, Controller
|
115
|
+
# => Posts
|
116
|
+
original_resource_name = qualified_parts[index_of_resource_type - 1]
|
117
|
+
resource_name = original_resource_name.pluralize
|
118
|
+
|
119
|
+
# Posts_Controller
|
120
|
+
# Post_Operations
|
121
|
+
named_resource_type = "#{original_resource_name}_#{resource_type}"
|
122
|
+
|
123
|
+
# Api, V2, Post, Operations, Update
|
124
|
+
# => Api, V2
|
125
|
+
namespace_index = index_of_resource_type - 1
|
126
|
+
namespace = namespace_index < 1 ? '' : qualified_parts.take(namespace_index)
|
127
|
+
|
128
|
+
# Api, V2, Post, Operations, Update
|
129
|
+
# => Update
|
130
|
+
class_index = index_of_resource_type + 1
|
131
|
+
class_path = class_index < 1 ? '' : qualified_parts.drop(class_index)
|
132
|
+
|
133
|
+
# Finally,
|
134
|
+
# build all the possible places that this file could be
|
135
|
+
path_options = [
|
136
|
+
|
137
|
+
# api/v2/posts/operations/update
|
138
|
+
to_path(namespace, resource_name, resource_type, class_path),
|
139
|
+
|
140
|
+
# api/v2/posts/post_operations/update
|
141
|
+
to_path(namespace, resource_name, named_resource_type, class_path),
|
142
|
+
|
143
|
+
# api/v2/posts/posts_controller
|
144
|
+
to_path(namespace, resource_name, named_resource_type),
|
145
|
+
|
146
|
+
# api/v2/posts/controller
|
147
|
+
to_path(namespace, resource_name, resource_type)
|
148
|
+
].uniq
|
149
|
+
|
150
|
+
file_path = ''
|
151
|
+
path_options.each do |path_option|
|
152
|
+
|
153
|
+
file_path = search_for_file(path_option)
|
154
|
+
|
155
|
+
break if file_path.present?
|
156
|
+
end
|
86
157
|
|
87
158
|
file_path
|
88
159
|
end
|
89
160
|
|
161
|
+
def to_path(*args)
|
162
|
+
args.flatten.reject(&:blank?).map(&:underscore).join('/')
|
163
|
+
end
|
164
|
+
|
90
165
|
# Load the constant named +const_name+ which is missing from +from_mod+. If
|
91
166
|
# it is not possible to load the constant into from_mod, try its parent
|
92
167
|
# module using +const_missing+.
|
93
168
|
def load_missing_constant(from_mod, const_name)
|
94
169
|
# always default to the actual implementation
|
95
170
|
super
|
96
|
-
rescue LoadError, NameError
|
171
|
+
rescue LoadError, NameError => e
|
172
|
+
load_missing_constant_error(from_mod, const_name, e)
|
173
|
+
end
|
97
174
|
|
175
|
+
# the heavy liftign of Rails Module Unification is just
|
176
|
+
# adding some additional pathfinding / constat lookup logic
|
177
|
+
# when the default (super) can't find what needs to be found
|
178
|
+
#
|
179
|
+
# @param [Class] from_mod - parent module / class that const_name may be a part of
|
180
|
+
# @param [Symbol] const_name - potential constant to lookup under from_mod
|
181
|
+
# @param [Exception] e - exception from previous error
|
182
|
+
def load_missing_constant_error(from_mod, const_name, e)
|
98
183
|
# examples
|
99
184
|
# - Api::PostsController
|
100
185
|
# - PostsController
|
101
|
-
qualified_name = qualified_name_for
|
102
|
-
|
186
|
+
qualified_name = qualified_name_for(from_mod, const_name)
|
103
187
|
file_path = resource_path_from_qualified_name(qualified_name)
|
104
188
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
189
|
+
begin
|
190
|
+
return load_from_path(file_path, qualified_name, from_mod, const_name) if file_path
|
191
|
+
rescue LoadError, NameError => e
|
192
|
+
# Recurse!
|
193
|
+
# not found, check the parent
|
194
|
+
at_the_top = from_mod.parent == from_mod
|
195
|
+
return load_missing_constant_error(from_mod.parent, const_name, e) unless at_the_top
|
196
|
+
raise e
|
197
|
+
end
|
113
198
|
|
114
|
-
name_error = NameError.new(
|
199
|
+
name_error = NameError.new(e.message)
|
115
200
|
name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ })
|
116
201
|
raise name_error
|
117
202
|
end
|
@@ -3,8 +3,17 @@ require 'rails/railtie'
|
|
3
3
|
|
4
4
|
module RailsModuleUnification
|
5
5
|
class Railtie < Rails::Railtie
|
6
|
+
railtie_name :rails_module_unification
|
7
|
+
|
8
|
+
rake_tasks do
|
9
|
+
load 'tasks/rails_module_unification.rake'
|
10
|
+
end
|
11
|
+
|
12
|
+
config_path = "#{Rails.root}/config/initializers/rails_module_unification"
|
13
|
+
config_exists = File.exist?(config_path)
|
14
|
+
require config_path if config_exists
|
15
|
+
|
6
16
|
initializer 'activeservice.autoload', before: :set_autoload_paths do |app|
|
7
|
-
# TODO: make the module unification root directory configurable
|
8
17
|
mu_dir = "#{Rails.root}/app/#{RailsModuleUnification.directory}"
|
9
18
|
|
10
19
|
# Data
|
@@ -0,0 +1,152 @@
|
|
1
|
+
namespace :rmu do
|
2
|
+
config_path = "#{Rails.root}/config/initializers/rails_module_unification"
|
3
|
+
config_exists = File.exist?(config_path)
|
4
|
+
require config_path if config_exists
|
5
|
+
|
6
|
+
# @example
|
7
|
+
# rake rmu:migrate_resource Post
|
8
|
+
# @example
|
9
|
+
# rake rmu:migrate_resource Api::Event
|
10
|
+
|
11
|
+
desc 'Moves all files related to a resource to the RMU directory'
|
12
|
+
task :migrate_resource, [:klass_name] => :environment do |_t, args|
|
13
|
+
klass_name = args[:klass_name]
|
14
|
+
|
15
|
+
# Given klass_name,
|
16
|
+
# check known places where related files could be
|
17
|
+
singular = possible_classes(klass_name)
|
18
|
+
plural = possible_classes(klass_name, plural: true)
|
19
|
+
|
20
|
+
possibilities = singular + plural
|
21
|
+
|
22
|
+
possibilities.each do |possible_class|
|
23
|
+
klass = possible_class.safe_constantize
|
24
|
+
|
25
|
+
next unless klass
|
26
|
+
|
27
|
+
location = location_of(klass)
|
28
|
+
|
29
|
+
unless location
|
30
|
+
puts "#{klass.name} could not be found"
|
31
|
+
next
|
32
|
+
end
|
33
|
+
|
34
|
+
next if already_moved?(location)
|
35
|
+
|
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
|
+
RailsModuleUnification.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
|
+
)
|
106
|
+
|
107
|
+
# PostsController
|
108
|
+
PLURAL_RESOURCE_SUFFIXES = %w(
|
109
|
+
Controller
|
110
|
+
)
|
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?("#{RailsModuleUnification.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
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_module_unification
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- L. Preston Sego III
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- lib/rails_module_unification/active_support_extensions.rb
|
163
163
|
- lib/rails_module_unification/railtie.rb
|
164
164
|
- lib/rails_module_unification/version.rb
|
165
|
+
- lib/tasks/rails_module_unification.rake
|
165
166
|
homepage: https://github.com/NullVoxPopuli/rails_module_unification
|
166
167
|
licenses:
|
167
168
|
- MIT
|
@@ -182,8 +183,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
183
|
version: '0'
|
183
184
|
requirements: []
|
184
185
|
rubyforge_project:
|
185
|
-
rubygems_version: 2.
|
186
|
+
rubygems_version: 2.6.7
|
186
187
|
signing_key:
|
187
188
|
specification_version: 4
|
188
|
-
summary: RailsModuleUnification-0.
|
189
|
+
summary: RailsModuleUnification-0.6.0
|
189
190
|
test_files: []
|