wallaby-core 0.3.0.beta2 → 0.3.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
  SHA256:
3
- metadata.gz: 86ff434d6a25fa665cb828cc1a1839166f386a71dff24afacb7a114402a81f50
4
- data.tar.gz: 1a923d492ef62f597608a9c49f8a1341fe59687e8c4668749f3ad25d7a1b1c71
3
+ metadata.gz: 78bd6d5518f084f9d968a6e880148186171a5d9e57155750d695c50a6cf40b70
4
+ data.tar.gz: b61387cb2a538b3befff1646adfdf2b9c207585b9bc0f4a42f56bb5e34465820
5
5
  SHA512:
6
- metadata.gz: 73183959d1177a5d707baae5c698e57f6e5ca47fe4b248e1014402dbe95c9015418d3d6d46194161c38543745c685a99bfacb73c197a1972e85beec758d8598c
7
- data.tar.gz: ffdeeb4e9df6b63f281912c21acb758e0a7bb9d8863d38a5e081b5c81cbfed5bf4942910ebf8d03fcef2c90cb8cd66f3bf0d9763302f59ae084003ea4f82dfb3
6
+ metadata.gz: ce828c53a527a2dae0fe51aeb81e1a95d3282242f7b324de4f87542a6c670f90938612e0c263677cb3f2d68287fa182081a4a931762886d22467cdde0ec99d05
7
+ data.tar.gz: 5a18a775749c109e769d285d09d188c49f370faf13bde75e3a0ca7626ec31b95060b35fa5d4fd3a44b7438d433ca9f56b5b7966aa3bcbc01b43ae7d7256ef21a
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @!visibility private
4
- # Defualt ability for {Wallaby}
4
+ # Default ability for {Wallaby}
5
5
  # If main app has defined `ability.rb`, this file will not be loaded/used.
6
6
  class Ability
7
7
  include ::CanCan::Ability if defined?(::CanCan)
data/config/routes.rb CHANGED
@@ -5,42 +5,56 @@ Wallaby::Engine.routes.draw do
5
5
  # @see Wallaby::ApplicationConcern#healthy
6
6
  get 'status', to: 'wallaby/resources#healthy'
7
7
 
8
- with_options to: Wallaby::ResourcesRouter.new do |route|
8
+ with_options to: Wallaby::ResourcesRouter.new do
9
9
  # @see Wallaby::ResourcesConcern#home
10
- route.root defaults: { action: 'home' }
10
+ root defaults: { action: 'home' }
11
11
 
12
12
  # Error pages for all supported HTTP status in {Wallaby::ERRORS}
13
13
  Wallaby::ERRORS.each do |status|
14
14
  code = Rack::Utils::SYMBOL_TO_STATUS_CODE[status]
15
- route.get status, defaults: { action: status.to_s }
16
- route.get code.to_s, defaults: { action: status.to_s }
15
+ get status, defaults: { action: status.to_s }
16
+ get code.to_s, defaults: { action: status.to_s }
17
17
  end
18
18
 
19
19
  # resourceful routes.
20
20
  #
21
- # `:resources` param here will be converted to the model class in the controller.
22
- # For instance, `"order::items"` will become `Order::Item` later,
21
+ # `*resources` param here will be converted to the model class
22
+ # used in the controller that [Wallaby::ResourcesRouter] dispatches to.
23
+ # For instance, `"order/items"` will become `Order::Item` later,
23
24
  # and `Order::Item` will be used by servicer/authorizer/paginator through out the whole request process.
24
25
  #
25
- # Using colons in the `:resources` param e.g. `"order::items"` instead of `"order/items"` is
26
- # to make nested namespace possible to be handled by these dynamic routes.
27
26
  # @see Wallaby::ResourcesRouter
28
- scope path: ':resources' do
29
- # @see Wallaby::ResourcesConcern#index
30
- route.get '', defaults: { action: 'index' }, as: :resources
31
- # @see Wallaby::ResourcesConcern#new
32
- route.get 'new', defaults: { action: 'new' }, as: :new_resource
33
- # @see Wallaby::ResourcesConcern#edit
34
- route.get ':id/edit', defaults: { action: 'edit' }, as: :edit_resource
35
- # @see Wallaby::ResourcesConcern#show
36
- route.get ':id', defaults: { action: 'show' }, as: :resource
37
-
38
- # @see Wallaby::ResourcesConcern#create
39
- route.post '', defaults: { action: 'create' }
40
- # @see Wallaby::ResourcesConcern#update
41
- route.match ':id', via: %i[patch put], defaults: { action: 'update' }
42
- # @see Wallaby::ResourcesConcern#destroy
43
- route.delete ':id', defaults: { action: 'destroy' }
27
+ constraints = {
28
+ id: Wallaby::LazyRegexp.new(:id_regexp),
29
+ resources: Wallaby::LazyRegexp.new(:resources_regexp)
30
+ }
31
+
32
+ scope path: '*resources' do
33
+ with_options(constraints: constraints) do
34
+ # NOTE: DO NOT change the order of the following routes!!!
35
+
36
+ # Exact match for `*resources/new`
37
+ # @see Wallaby::ResourcesConcern#new
38
+ get 'new', defaults: { action: 'new' }, as: :new_resource
39
+
40
+ # Match `*resources`
41
+ # `''` needs to be before `':id'` so that the resourcesful route will match `orders/items` with:
42
+ # `resources: 'orders/items'` instead of `resources: 'orders', id: 'items'`
43
+ # @see Wallaby::ResourcesConcern#index
44
+ get '', defaults: { action: 'index' }, as: :resources
45
+ # @see Wallaby::ResourcesConcern#create
46
+ post '', defaults: { action: 'create' }
47
+
48
+ # Match `*resources/:id`
49
+ # @see Wallaby::ResourcesConcern#edit
50
+ get ':id/edit', defaults: { action: 'edit' }, as: :edit_resource
51
+ # @see Wallaby::ResourcesConcern#show
52
+ get ':id', defaults: { action: 'show' }, as: :resource
53
+ # @see Wallaby::ResourcesConcern#update
54
+ match ':id', via: %i[patch put], defaults: { action: 'update' }
55
+ # @see Wallaby::ResourcesConcern#destroy
56
+ delete ':id', defaults: { action: 'destroy' }
57
+ end
44
58
  end
45
59
  end
46
60
  end
@@ -9,9 +9,9 @@ module Wallaby
9
9
  def fields
10
10
  @fields ||=
11
11
  ::ActiveSupport::HashWithIndifferentAccess.new.tap do |hash|
12
- methods = model_class.public_instance_methods(false).map(&:to_s)
12
+ methods = model_class.public_instance_methods.map(&:to_s)
13
13
  methods
14
- .grep(/[^=]$/).select { |method_id| methods.include? "#{method_id}=" }
14
+ .grep(/(\A[^!=]|[^!=]\Z)/).select { |method_id| methods.include? "#{method_id}=" }
15
15
  .each { |attribute| hash[attribute] = { label: attribute.humanize, type: 'string' } }
16
16
  end.freeze
17
17
  end
@@ -45,7 +45,7 @@ module Wallaby
45
45
 
46
46
  # @return [String, Symbole] default to `:id`
47
47
  def primary_key
48
- @primary_key ||= :id
48
+ @primary_key ||= fields.key?(:id) ? :id : fields.keys.first.try(:to_sym)
49
49
  end
50
50
 
51
51
  # @param resource [Object]
@@ -20,7 +20,7 @@ module Wallaby
20
20
  content_tag :ul, class: 'errors' do
21
21
  errors.each do |message|
22
22
  concat content_tag :li, content_tag(
23
- :small, raw(message) # rubocop:disable Rails/OutputSafety
23
+ :small, raw(message)
24
24
  )
25
25
  end
26
26
  end
@@ -7,7 +7,7 @@ module Wallaby
7
7
  include Fieldable
8
8
 
9
9
  MISSING_METHODS_RELATED_TO_FIELDS =
10
- /\A(?<prefix>[a-zA-Z]\w*_)(?<method_partial>fields=?|field_names=?|metadata_of|label_of|type_of)\Z/.freeze
10
+ /\A(?<method_prefix>[a-zA-Z]\w*_)(?<method_suffix>fields=?|field_names=?|metadata_of|label_of|type_of)\Z/.freeze
11
11
 
12
12
  # Initialize with model class
13
13
  # @param model_class [Class]
@@ -123,7 +123,7 @@ module Wallaby
123
123
  return super unless method_name.match?(MISSING_METHODS_RELATED_TO_FIELDS)
124
124
 
125
125
  matched = MISSING_METHODS_RELATED_TO_FIELDS.match(method_name)
126
- try("prefix_#{matched[:method_partial]}", *args, matched[:prefix], &block)
126
+ try("prefix_#{matched[:method_suffix]}", *args, matched[:method_prefix], &block)
127
127
  end
128
128
 
129
129
  # Check if method looks like: `_fields`, `_field_names` that can be processed by {Fieldable}
@@ -150,19 +150,20 @@ module Wallaby
150
150
  # @return [String, Symbol] type
151
151
  # @raise [ArgumentError] when type is nil
152
152
  def ensure_type_is_present(field_name, type, prefix = '')
153
- type || raise(::ArgumentError, <<~INSTRUCTION
154
- The type for field `#{field_name}` is missing in metadata `#{prefix}_fields`.
155
- The possible causes are:
153
+ type || raise(::ArgumentError, <<~INSTRUCTION)
154
+ The field `#{field_name}` is missing its type definition. Potential causes include:
156
155
 
157
- 1. Check type's value from metadata `#{prefix}_fields[:#{field_name}][:type]`.
158
- If it is missing, specify the type as below:
156
+ 1. Check the type value from the metadata `#{prefix}fields[:#{field_name}][:type]`.
157
+ If it is absent, specify the type as shown in the example below:
159
158
 
160
- #{prefix}fields[:#{field_name}][:type] = 'string'
159
+ #{prefix}fields[:#{field_name}][:type] = 'string' # or the specified type
161
160
 
162
- 2. If metadata `#{prefix}_fields` is blank, maybe table hasn't be created yet
163
- or there is some error in the decorator class declaration.
161
+ 2. If the metadata `#{prefix}fields[:#{field_name}]` is empty, the field may not exist.
162
+ Search for `index_field_names` and `#{field_name}` to determine if `#{field_name}` is inserted elsewhere.
163
+
164
+ 3. If the metadata `#{prefix}fields` is empty, the table may not have been created yet,
165
+ or there may be an error in the decorator class declaration.
164
166
  INSTRUCTION
165
- )
166
167
  end
167
168
  end
168
169
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class IdRegexp < ResourcesRegexp
5
+ # This method works with {Map.resources_regexp} to complete the constraint restriction in `config/routes.rb`.
6
+ # This regular expression matches the ids which have all the possible resources names in front
7
+ #
8
+ # It looks like `((?<=products/)|(?<=orders/)|(?<=order/items/)|...|(?<!.))[^/]+`:
9
+ #
10
+ # - `(?<=products/)` is a positive lookbehind assertion,
11
+ # it means the ids must have `products/` in front of itself, but the match data won't include `products/`.
12
+ # it matches string e.g. `/admin/products/1`, and the match data is `1`.
13
+ # - `(?<!.)` is a negative lookbehind assertion,
14
+ # it means the ids must have nothing in front of itself.
15
+ # it matches string e.g. `1`, and the match data is `1`.
16
+ # this is required for URL helper when `:id` param is given,
17
+ # e.g. `resources_path(action: 'show', resources: 'products', id: 1)`
18
+ # - `[^/]+` is to match id. id can be anything as long as it doesn't contain `|` character.
19
+ def execute
20
+ Regexp.new(<<~REGEXP, Regexp::EXTENDED)
21
+ (
22
+ #{resources_sources.map { |resources| "(?<=#{resources}/)" }.join('|')} # all the possible resources names in front of the id
23
+ |(?<!.) # nothing is in front of the id, this is needed by URL helpers
24
+ )
25
+ [^/]+ # id
26
+ REGEXP
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # This is designed to delegate all the methods to {#lazy_regexp}
5
+ # So that it doesn't need to load all the models for {Map.mode_map}
6
+ # when the engine is mounted in `config/routes.rb`
7
+ #
8
+ # @see Map.resources_regexp
9
+ # @see Map.id_regexp
10
+ class LazyRegexp < Regexp
11
+ delegate(*Regexp.instance_methods(false), to: :lazy_regexp)
12
+
13
+ def initialize(source, **options)
14
+ @lazy_source = source
15
+ super(source.to_s, **options)
16
+ end
17
+
18
+ protected
19
+
20
+ def lazy_regexp
21
+ Map.try(@lazy_source)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class ResourcesRegexp
5
+ # This method works with {Map.id_regexp} to complete the constraint restriction in `config/routes.rb`.
6
+ # This regular expression matches all the possible resources names that are loaded from the {.mode_map}
7
+ #
8
+ # It looks like `(products|orders|order/items|...)`
9
+ def execute
10
+ Regexp.new(<<~REGEXP, Regexp::EXTENDED)
11
+ (#{resources_sources.join('|')}) # all the possible resources names
12
+ REGEXP
13
+ end
14
+
15
+ protected
16
+
17
+ def resources_sources
18
+ # NOTE: `.reverse` is required so that for `order/items`,
19
+ # `%r{order/items|order}` matches `order/items`, not `order`
20
+ Map
21
+ .mode_map
22
+ .keys.flat_map { |klass| Inflector.to_resources_name(klass.to_s) }
23
+ .sort.reverse.uniq
24
+ end
25
+ end
26
+ end
@@ -31,7 +31,7 @@ module Wallaby
31
31
  controller_class = find_controller_class_by(options)
32
32
  controller_class.action(options[:action]).call(env)
33
33
  rescue ::AbstractController::ActionNotFound, ModelNotFound => e
34
- set_flash_error_for(e, env)
34
+ Wallaby::Logger.warn(e, sourcing: 1)
35
35
  default_controller(options).action(:not_found).call(env)
36
36
  rescue UnprocessableEntity => e
37
37
  set_flash_error_for(e, env)
@@ -65,7 +65,7 @@ module Wallaby
65
65
  return EMPTY_STRING if name.blank?
66
66
 
67
67
  name = name.to_s unless name.is_a?(String)
68
- name.tableize.gsub(SLASH, COLONS)
68
+ name.tableize
69
69
  end
70
70
 
71
71
  # @param name [Class, String]
@@ -7,10 +7,10 @@ module Wallaby
7
7
 
8
8
  # Convert Class to String. If not Class, unchanged.
9
9
  # @param klass [Object]
10
- # @return [String] if klass is a Class
11
- # @return [Object] if klass is not a Class
10
+ # @return [String] if klass is not nil
11
+ # @return [nil] if klass is nil or klass is an anonymous Class
12
12
  def class_name_of(klass)
13
- klass.try(:name) || klass || nil
13
+ klass.is_a?(Class) ? klass.try(:name) : klass.try(:to_s)
14
14
  end
15
15
 
16
16
  # Convert String to Class. If not String, unchanged.
@@ -18,13 +18,21 @@ module Wallaby
18
18
  # @return [Class] if name is a Class
19
19
  # @return [Object] if name is not a String
20
20
  # @return [nil] if class cannot be found
21
- def to_class(name)
22
- return name unless name.is_a? String
21
+ def to_class(name, raising: Wallaby.configuration.raise_on_name_error)
22
+ return name unless name.is_a?(String)
23
+ # blank string will lead to NameError `wrong constant name`
24
+ return if name.blank?
23
25
 
24
- # NOTE: DO NOT try to use const_defined? and const_get EVER.
25
- # This is Rails, use constantize
26
+ # NOTE: DO NOT try to use `const_defined?` and `const_get` EVER.
27
+ # Rails does all the class loading magics using `constantize`
26
28
  name.constantize
27
- rescue NameError
29
+ rescue NameError => e
30
+ raise if raising
31
+
32
+ uninitialized = e.message.start_with?('uninitialized constant')
33
+ raise unless uninitialized
34
+
35
+ # block to handle this missing constant, e.g. use a default class or log useful instruction
28
36
  yield(name) if block_given?
29
37
  end
30
38
  end
@@ -6,6 +6,9 @@ module Wallaby
6
6
  class Configuration
7
7
  include Classifier
8
8
 
9
+ # @!attribute [w] raise_on_name_error
10
+ attr_accessor :raise_on_name_error
11
+
9
12
  # @!attribute [w] logger
10
13
  attr_writer :logger
11
14
 
@@ -101,6 +104,7 @@ module Wallaby
101
104
  Deprecator.alert 'config.models.presence', from: '0.3.0', alternative: <<~INSTRUCTION
102
105
  Please use controller_class.models instead.
103
106
  INSTRUCTION
107
+ @models ||= Models.new
104
108
  end
105
109
 
106
110
  # To globally configure the models that Wallaby should handle.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Wallaby
4
4
  module Core
5
- VERSION = '0.3.0.beta2' # :nodoc:
5
+ VERSION = '0.3.0' # :nodoc:
6
6
  end
7
7
  end
data/lib/wallaby/core.rb CHANGED
@@ -30,6 +30,9 @@ require 'wallaby/map'
30
30
 
31
31
  require 'support/action_dispatch/routing/mapper'
32
32
 
33
+ require 'routes/wallaby/lazy_regexp'
34
+ require 'routes/wallaby/resources_regexp'
35
+ require 'routes/wallaby/id_regexp'
33
36
  require 'routes/wallaby/resources_router'
34
37
  require 'routes/wallaby/engines/base_route'
35
38
  require 'routes/wallaby/engines/engine_route'
data/lib/wallaby/map.rb CHANGED
@@ -90,6 +90,18 @@ module Wallaby
90
90
  end
91
91
  end
92
92
 
93
+ class << self
94
+ # @return [Regexp] regexp to match data for param `:resources`
95
+ def resources_regexp
96
+ @resources_regexp ||= ResourcesRegexp.new.execute
97
+ end
98
+
99
+ # @return [Regexp] regexp to match data for param `:id`
100
+ def id_regexp
101
+ @id_regexp ||= IdRegexp.new.execute
102
+ end
103
+ end
104
+
93
105
  class << self
94
106
  # Reset all the instance variables to nil
95
107
  def clear
@@ -33,6 +33,7 @@ module Wallaby
33
33
  @eager_load_paths ||=
34
34
  Rails.configuration.paths['app'].expanded
35
35
  .concat(Rails.configuration.eager_load_paths)
36
+ .uniq
36
37
  end
37
38
 
38
39
  # @!attribute [w] model_paths
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wallaby-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.beta2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
- - Tian Chen
7
+ - Tianwen Chen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-21 00:00:00.000000000 Z
11
+ date: 2024-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -17,6 +17,9 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 6.0.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 8.0.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -24,6 +27,9 @@ dependencies:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: 6.0.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 8.0.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: railties
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -267,6 +273,9 @@ files:
267
273
  - lib/routes/wallaby/engines/base_route.rb
268
274
  - lib/routes/wallaby/engines/custom_app_route.rb
269
275
  - lib/routes/wallaby/engines/engine_route.rb
276
+ - lib/routes/wallaby/id_regexp.rb
277
+ - lib/routes/wallaby/lazy_regexp.rb
278
+ - lib/routes/wallaby/resources_regexp.rb
270
279
  - lib/routes/wallaby/resources_router.rb
271
280
  - lib/servicers/wallaby/model_servicer.rb
272
281
  - lib/services/wallaby/authorizer_finder.rb
@@ -317,13 +326,13 @@ files:
317
326
  - lib/wallaby/logger.rb
318
327
  - lib/wallaby/map.rb
319
328
  - lib/wallaby/preloader.rb
320
- homepage: https://github.com/wallaby-rails/wallaby-core
329
+ homepage: https://github.com/wallaby-rails/wallaby-rails/blob/main/wallaby-core
321
330
  licenses:
322
331
  - MIT
323
332
  metadata:
324
- homepage_uri: https://github.com/wallaby-rails/wallaby-core
325
- source_code_uri: https://github.com/wallaby-rails/wallaby-core
326
- changelog_uri: https://github.com/wallaby-rails/wallaby-core/blob/master/CHANGELOG.md
333
+ homepage_uri: https://github.com/wallaby-rails/wallaby-rails/blob/main/wallaby-core
334
+ source_code_uri: https://github.com/wallaby-rails/wallaby-rails/blob/main/wallaby-core
335
+ changelog_uri: https://github.com/wallaby-rails/wallaby-rails/blob/main/wallaby-core/CHANGELOG.md
327
336
  rubygems_mfa_required: 'true'
328
337
  post_install_message:
329
338
  rdoc_options: []