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: 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: []