rails_module_unification 0.5.3 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0d3872640062b5d3a147e451410b3a797b512773
4
- data.tar.gz: 4c20943150ad3386792e31960cbb14326270b9b4
3
+ metadata.gz: 9ec1fa46bb3f2a6ff70fa6a4752fe649118ab97f
4
+ data.tar.gz: 62de027845335d0a7cf3e5d497b6b0f470cb340f
5
5
  SHA512:
6
- metadata.gz: 4956f83bb5cea1d755c5d8ad13780fb5cab54d331ee59f4af8ab4bed20f7fcd08c0c51364824774a7567c5ddfe8cd0e16b18b635a022c41f9dae35c9e6a194f5
7
- data.tar.gz: 0bf4109a287d04d4e7a62723aff8165895f55006613f223599c9082593516cb61a045c38a90ba74d7f2e8e8a3324229a8a7e8fcf11c46969765caaf20aa1e41e
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 so that you can gradually migrate to the new structure over time.
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
- RESOURCE_SUFFIXES = /(Controller|Serializer|Operation|Policy)/
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
- raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false)
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
- def load_from_parent(from_mod, const_name)
20
- # If our parents do not have a constant named +const_name+ then we are free
21
- # to attempt to load upwards. If they do have such a constant, then this
22
- # const_missing must be due to from_mod::const_name, which should not
23
- # return constants from from_mod's parents.
24
- parent = from_mod.parent
25
- present_in_ancestry = (
26
- parent &&
27
- parent != from_mod &&
28
- !from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
29
- )
30
-
31
- # Since Ruby does not pass the nesting at the point the unknown
32
- # constant triggered the callback we cannot fully emulate constant
33
- # name lookup and need to make a trade-off: we are going to assume
34
- # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even
35
- # though it might not be. Counterexamples are
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
- # class Foo::Bar
38
- # Module.nesting # => [Foo::Bar]
39
- # end
82
+ # Api::Posts
83
+ # => Api, Posts
40
84
  #
41
- # or
85
+ # Api::PostOperations::Create
86
+ # => Api, Post, Operations, Create
42
87
  #
43
- # module M::N
44
- # module S::T
45
- # Module.nesting # => [S::T, M::N]
46
- # end
47
- # end
88
+ # Api::PostsController
89
+ # => Api, Posts, Controller
48
90
  #
49
- # for example.
50
- return parent.const_missing(const_name) if present_in_ancestry
51
- rescue NameError => e
52
- raise unless e.missing_name? qualified_name_for(parent, const_name)
53
- end
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
- def resource_path_from_qualified_name(qualified_name)
56
- # examples
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
- # examples
62
- # - controller
63
- # - serializer
64
- type_name = file_name.split('_').last
65
-
66
- # folder/named_type.rb
67
- # examples:
68
- # - api/posts
69
- # - posts
70
- folder_name = qualified_name.split(RESOURCE_SUFFIXES).first.underscore.pluralize
71
-
72
- # examples:
73
- # - posts/posts_controller
74
- folder_named_type = folder_name + '/' + file_name
75
-
76
- # folder/type.rb
77
- folder_type_name = folder_name + '/' + type_name
78
-
79
- # without a folder / namespace?
80
- # TODO: could this have undesired consequences?
81
- file_path = search_for_file(file_name)
82
- # the resource_name/controller.rb naming scheme
83
- file_path ||= search_for_file(folder_type_name)
84
- # the resource_name/resource_names_controller.rb naming scheme
85
- file_path ||= search_for_file(folder_named_type)
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 from_mod, const_name
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
- return load_from_path(file_path, qualified_name, from_mod, const_name) if file_path
106
-
107
- # TODO: what is the situation in which this is needed?
108
- mod = autoload_module!(from_mod, const_name, qualified_name, file_name)
109
- return mod if mod
110
-
111
- from_parent = load_from_parent(from_mod, const_name)
112
- return from_parent if from_parent
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("uninitialized constant #{qualified_name}", const_name)
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module RailsModuleUnification
3
- VERSION = '0.5.3'.freeze
3
+ VERSION = '0.6.0'.freeze
4
4
  end
@@ -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.5.3
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-09-18 00:00:00.000000000 Z
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.5.1
186
+ rubygems_version: 2.6.7
186
187
  signing_key:
187
188
  specification_version: 4
188
- summary: RailsModuleUnification-0.5.3
189
+ summary: RailsModuleUnification-0.6.0
189
190
  test_files: []