shaf 1.6.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/iana_link_relations.csv.gz +0 -0
- data/lib/shaf.rb +6 -0
- data/lib/shaf/alps/attribute_serializer.rb +41 -0
- data/lib/shaf/alps/json_serializer.rb +50 -0
- data/lib/shaf/alps/relation_serializer.rb +70 -0
- data/lib/shaf/api_doc/link_relations.rb +77 -0
- data/lib/shaf/authenticator.rb +56 -0
- data/lib/shaf/authenticator/base.rb +161 -0
- data/lib/shaf/authenticator/basic_auth.rb +25 -0
- data/lib/shaf/authenticator/challenge.rb +32 -0
- data/lib/shaf/authenticator/parameter.rb +31 -0
- data/lib/shaf/authenticator/request.rb +17 -0
- data/lib/shaf/command/console.rb +1 -1
- data/lib/shaf/command/generate.rb +5 -2
- data/lib/shaf/command/new.rb +20 -7
- data/lib/shaf/command/templates/Gemfile.erb +1 -0
- data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +1 -5
- data/lib/shaf/errors.rb +11 -0
- data/lib/shaf/extensions.rb +3 -3
- data/lib/shaf/extensions/api_routes.rb +60 -0
- data/lib/shaf/extensions/authorize.rb +11 -9
- data/lib/shaf/extensions/log.rb +1 -1
- data/lib/shaf/extensions/resource_uris.rb +139 -63
- data/lib/shaf/extensions/symbolic_routes.rb +22 -19
- data/lib/shaf/formable.rb +1 -2
- data/lib/shaf/formable/form.rb +1 -1
- data/lib/shaf/generator.rb +2 -0
- data/lib/shaf/generator/base.rb +2 -3
- data/lib/shaf/generator/controller.rb +11 -7
- data/lib/shaf/generator/doc.rb +17 -0
- data/lib/shaf/generator/forms.rb +1 -0
- data/lib/shaf/generator/helper.rb +2 -1
- data/lib/shaf/generator/migration/base.rb +7 -3
- data/lib/shaf/generator/migration/type.rb +4 -26
- data/lib/shaf/generator/migration/types.rb +45 -16
- data/lib/shaf/generator/model.rb +1 -2
- data/lib/shaf/generator/profile.rb +52 -0
- data/lib/shaf/generator/serializer.rb +38 -73
- data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -2
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +1 -2
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
- data/lib/shaf/helpers.rb +4 -0
- data/lib/shaf/helpers/authentication.rb +79 -0
- data/lib/shaf/helpers/payload.rb +14 -34
- data/lib/shaf/helpers/vary.rb +8 -0
- data/lib/shaf/logger.rb +12 -0
- data/lib/shaf/parser.rb +65 -0
- data/lib/shaf/parser/base.rb +44 -0
- data/lib/shaf/parser/form_data.rb +15 -0
- data/lib/shaf/parser/json.rb +26 -0
- data/lib/shaf/profile.rb +110 -0
- data/lib/shaf/profile/attribute.rb +29 -0
- data/lib/shaf/profile/evaluator.rb +46 -0
- data/lib/shaf/profile/relation.rb +29 -0
- data/lib/shaf/profile/unique_id.rb +58 -0
- data/lib/shaf/profiles.rb +42 -0
- data/lib/shaf/profiles/shaf_basic.rb +20 -0
- data/lib/shaf/profiles/shaf_error.rb +48 -0
- data/lib/shaf/profiles/shaf_form.rb +109 -0
- data/lib/shaf/responder.rb +41 -2
- data/lib/shaf/responder/alps_json.rb +25 -0
- data/lib/shaf/responder/base.rb +20 -17
- data/lib/shaf/responder/hal.rb +65 -4
- data/lib/shaf/responder/html.rb +43 -16
- data/lib/shaf/responder/problem_json.rb +1 -1
- data/lib/shaf/serializer.rb +31 -0
- data/lib/shaf/settings.rb +25 -12
- data/lib/shaf/spec.rb +1 -0
- data/lib/shaf/spec/authenticator.rb +13 -0
- data/lib/shaf/spec/base.rb +1 -1
- data/lib/shaf/spec/http_method_utils.rb +1 -1
- data/lib/shaf/spec/integration_spec.rb +25 -13
- data/lib/shaf/spec/payload_utils.rb +2 -2
- data/lib/shaf/supported_http_methods.rb +15 -0
- data/lib/shaf/tasks/api_doc_task.rb +24 -3
- data/lib/shaf/tasks/routes_task.rb +14 -17
- data/lib/shaf/upgrade/manifest.rb +11 -2
- data/lib/shaf/upgrade/package.rb +73 -43
- data/lib/shaf/upgrade/version.rb +11 -10
- data/lib/shaf/utils.rb +19 -5
- data/lib/shaf/version.rb +3 -1
- data/lib/shaf/yard.rb +34 -0
- data/lib/shaf/yard/attribute_method_handler.rb +19 -0
- data/lib/shaf/yard/attribute_object.rb +30 -0
- data/lib/shaf/yard/base_method_handler.rb +30 -0
- data/lib/shaf/yard/link_method_handler.rb +39 -0
- data/lib/shaf/yard/link_object.rb +60 -0
- data/lib/shaf/yard/nested_attributes.rb +37 -0
- data/lib/shaf/yard/parser.rb +64 -0
- data/lib/shaf/yard/profile_method_handler.rb +51 -0
- data/lib/shaf/yard/profile_object.rb +21 -0
- data/lib/shaf/yard/resource_object.rb +55 -0
- data/lib/shaf/yard/serializer_handler.rb +27 -0
- data/templates/api/controllers/base_controller.rb +0 -10
- data/templates/api/controllers/docs_controller.rb +5 -3
- data/templates/api/controllers/root_controller.rb +7 -1
- data/templates/api/policies/base_policy.rb +2 -0
- data/templates/api/serializers/base_serializer.rb +1 -3
- data/templates/api/serializers/error_serializer.rb +1 -5
- data/templates/api/serializers/form_serializer.rb +1 -5
- data/templates/api/serializers/validation_error_serializer.rb +1 -5
- data/templates/config/bootstrap.rb +1 -2
- data/templates/config/directories.rb +52 -44
- data/templates/config/helpers.rb +1 -1
- data/templates/config/initializers.rb +52 -8
- data/templates/config/initializers/authentication.rb +18 -0
- data/templates/config/initializers/db_migrations.rb +2 -2
- data/templates/config/initializers/logging.rb +2 -2
- data/templates/spec/spec_helper.rb +2 -0
- data/upgrades/0.5.0.tar.gz +0 -0
- data/upgrades/1.0.4.tar.gz +0 -0
- data/upgrades/1.1.0.tar.gz +0 -0
- data/upgrades/2.0.0.tar.gz +0 -0
- data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
- data/yard_templates/api_doc/doc_index/setup.rb +8 -0
- data/yard_templates/api_doc/html/css/api-doc.css +222 -0
- data/yard_templates/api_doc/html/favicon.ico +0 -0
- data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
- data/yard_templates/api_doc/html/setup.rb +59 -0
- data/yard_templates/api_doc/layout/html/footer.erb +3 -0
- data/yard_templates/api_doc/layout/html/header.erb +7 -0
- data/yard_templates/api_doc/layout/html/layout.erb +13 -0
- data/yard_templates/api_doc/layout/setup.rb +24 -0
- data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
- data/yard_templates/api_doc/profile/html/profile.erb +6 -0
- data/yard_templates/api_doc/profile/html/relations.erb +10 -0
- data/yard_templates/api_doc/profile/setup.rb +38 -0
- data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
- data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
- data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
- data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
- data/yard_templates/api_doc/resource/html/profile.erb +14 -0
- data/yard_templates/api_doc/resource/html/relations.erb +10 -0
- data/yard_templates/api_doc/resource/html/resource.erb +5 -0
- data/yard_templates/api_doc/resource/setup.rb +56 -0
- data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
- data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
- data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
- data/yard_templates/api_doc/setup.rb +31 -0
- data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
- data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
- data/yard_templates/api_doc/sidebar/setup.rb +56 -0
- metadata +137 -30
- metadata.gz.sig +1 -3
- data/lib/shaf/extensions/current_user.rb +0 -48
- data/lib/shaf/responder/hal_serializable.rb +0 -64
@@ -0,0 +1,25 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Authenticator
|
3
|
+
class BasicAuth < Base
|
4
|
+
scheme 'Basic'
|
5
|
+
|
6
|
+
param :realm
|
7
|
+
param :charset, required: false, values: ["UTF-8"]
|
8
|
+
|
9
|
+
def self.credentials(authorization, _request)
|
10
|
+
return unless authorization
|
11
|
+
|
12
|
+
decoded = String(authorization.unpack("m*").first)
|
13
|
+
return {} if decoded.empty?
|
14
|
+
|
15
|
+
user, password = decoded.split(/:/, 2)
|
16
|
+
.map { |str| str unless String(str).empty? }
|
17
|
+
|
18
|
+
{
|
19
|
+
user: user,
|
20
|
+
password: password
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Authenticator
|
5
|
+
class Challenge
|
6
|
+
attr_reader :scheme, :parameters, :realm
|
7
|
+
|
8
|
+
def initialize(scheme, **parameters, &block)
|
9
|
+
@scheme = scheme
|
10
|
+
@realm = parameters.delete(:realm)&.to_s
|
11
|
+
@parameters = parameters
|
12
|
+
define_singleton_method(:test, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{scheme} #{parameter_string}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def realm?(arg)
|
20
|
+
realm&.to_s == arg&.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parameter_string
|
26
|
+
params = {}
|
27
|
+
params[:realm] = realm if realm
|
28
|
+
params.merge(parameters).map { |k,v| %Q(#{k}="#{v}") }.join(', ')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Authenticator
|
5
|
+
class Parameter
|
6
|
+
attr_reader :name, :default, :values
|
7
|
+
|
8
|
+
def initialize(name, required: true, default: nil, values: nil)
|
9
|
+
@name = name
|
10
|
+
@required = required
|
11
|
+
@default = default
|
12
|
+
@values = values&.map(&:downcase)
|
13
|
+
end
|
14
|
+
|
15
|
+
def required?
|
16
|
+
@required
|
17
|
+
end
|
18
|
+
|
19
|
+
def optional?
|
20
|
+
!required?
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?(value)
|
24
|
+
return optional? if value.nil?
|
25
|
+
return true unless values
|
26
|
+
|
27
|
+
values.include?(value.downcase)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rack/auth/abstract/request'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Authenticator
|
5
|
+
class Request < Rack::Auth::AbstractRequest
|
6
|
+
attr_reader :env
|
7
|
+
|
8
|
+
def valid?
|
9
|
+
!String(authorization).strip.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
def authorization
|
13
|
+
env[authorization_key]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/shaf/command/console.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'file_transactions'
|
1
2
|
require 'shaf/generator'
|
2
3
|
|
3
4
|
module Shaf
|
4
5
|
module Command
|
5
6
|
class Generate < Base
|
6
7
|
|
7
|
-
identifier %r(\Ag(en(erate)
|
8
|
+
identifier %r(\Ag(\b|en(\b|erate))\Z)
|
8
9
|
usage Generator::Factory.usage.flatten.sort
|
9
10
|
|
10
11
|
def self.options(parser, options)
|
@@ -19,7 +20,9 @@ module Shaf
|
|
19
20
|
|
20
21
|
def call
|
21
22
|
in_project_root do
|
22
|
-
|
23
|
+
FileTransactions.transaction do
|
24
|
+
Generator::Factory.create(*args, **options).call
|
25
|
+
end
|
23
26
|
end
|
24
27
|
rescue StandardError => e
|
25
28
|
raise Command::ArgumentError, e.message
|
data/lib/shaf/command/new.rb
CHANGED
@@ -10,21 +10,26 @@ module Shaf
|
|
10
10
|
usage 'new PROJECT_NAME'
|
11
11
|
|
12
12
|
def call
|
13
|
-
|
14
|
-
if
|
13
|
+
self.project_name = args.first
|
14
|
+
if project_name.nil? || project_name.empty?
|
15
15
|
raise ArgumentError,
|
16
16
|
"Please provide a project name when using command 'new'!"
|
17
17
|
end
|
18
18
|
|
19
|
-
create_dir
|
20
|
-
Dir.chdir(
|
19
|
+
create_dir project_name
|
20
|
+
Dir.chdir(project_name) do
|
21
21
|
copy_templates
|
22
22
|
create_gemfile
|
23
|
+
create_settings_file
|
23
24
|
write_shaf_version
|
24
25
|
create_ruby_version_file
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :project_name
|
32
|
+
|
28
33
|
def create_dir(name)
|
29
34
|
return if Dir.exist? name
|
30
35
|
FileUtils.mkdir_p(name)
|
@@ -38,9 +43,17 @@ module Shaf
|
|
38
43
|
File.write "Gemfile", erb(content)
|
39
44
|
end
|
40
45
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
46
|
+
def create_settings_file
|
47
|
+
settings_file = 'config/settings.yml'
|
48
|
+
template_file = File.expand_path("../templates/#{settings_file}.erb", __FILE__)
|
49
|
+
content = File.read(template_file)
|
50
|
+
File.write settings_file,
|
51
|
+
erb(content, project_name: project_name.capitalize)
|
52
|
+
end
|
53
|
+
|
54
|
+
def erb(content, locals = {})
|
55
|
+
return ERB.new(content, 0, '%-<>').result_with_hash(locals) if RUBY_VERSION < "2.6.0"
|
56
|
+
ERB.new(content, trim_mode: '-<>').result_with_hash(locals)
|
44
57
|
end
|
45
58
|
|
46
59
|
def copy_templates
|
@@ -1,5 +1,6 @@
|
|
1
1
|
---
|
2
2
|
default: &default
|
3
|
+
project_name: <%= project_name %>
|
3
4
|
public_folder: frontend/assets
|
4
5
|
views_folder: frontend/views
|
5
6
|
documents_dir: doc/api
|
@@ -12,11 +13,6 @@ default: &default
|
|
12
13
|
hostname: localhost
|
13
14
|
protocol: http
|
14
15
|
port: 3000
|
15
|
-
auth_token_header: X-Auth-Token
|
16
|
-
form_profile_name: shaf-form
|
17
|
-
form_profile_uri: https://gist.githubusercontent.com/sammyhenningsson/39c8aafeaf60192b082762cbf3e08d57/raw/shaf-form.md
|
18
|
-
error_profile_name: shaf-error
|
19
|
-
error_profile_uri: https://gist.githubusercontent.com/sammyhenningsson/049d10e2b8978059cde104fc5d6c2d52/raw/shaf-error.md
|
20
16
|
|
21
17
|
production:
|
22
18
|
<<: *default
|
data/lib/shaf/errors.rb
CHANGED
@@ -64,6 +64,17 @@ module Shaf
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
+
class NotAcceptableError < ServerError
|
68
|
+
def http_status
|
69
|
+
406
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(msg = nil)
|
73
|
+
msg ||= 'Resource found, but a suitable representation could not be generated'
|
74
|
+
super(msg, code: 'NOT_ACCEPTABLE', title: 'Content negotiation failed')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
67
78
|
class ConflictError < ServerError
|
68
79
|
def http_status
|
69
80
|
409
|
data/lib/shaf/extensions.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'shaf/extensions/log'
|
2
2
|
require 'shaf/extensions/resource_uris'
|
3
3
|
require 'shaf/extensions/controller_hooks'
|
4
|
-
require 'shaf/extensions/current_user'
|
5
4
|
require 'shaf/extensions/authorize'
|
6
5
|
require 'shaf/extensions/symbolic_routes'
|
6
|
+
require 'shaf/extensions/api_routes'
|
7
7
|
|
8
8
|
module Shaf
|
9
9
|
def self.extensions
|
@@ -11,9 +11,9 @@ module Shaf
|
|
11
11
|
Log,
|
12
12
|
ResourceUris,
|
13
13
|
ControllerHooks,
|
14
|
-
CurrentUser,
|
15
14
|
Authorize,
|
16
|
-
SymbolicRoutes
|
15
|
+
SymbolicRoutes,
|
16
|
+
ApiRoutes # This extension must be registered after `SymbolicRoutes`!
|
17
17
|
]
|
18
18
|
end
|
19
19
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module ApiRoutes
|
5
|
+
class Registry
|
6
|
+
class << self
|
7
|
+
def register(controller, method, symbol)
|
8
|
+
routes[controller][symbol] << method.to_s.upcase
|
9
|
+
end
|
10
|
+
|
11
|
+
def controllers
|
12
|
+
routes.keys.sort_by(&:to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def routes_for(controller)
|
16
|
+
sorted = routes[controller].keys.sort_by(&:to_s)
|
17
|
+
sorted.each do |symbol|
|
18
|
+
yield route_info(controller, symbol)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def routes
|
25
|
+
@routes ||= Hash.new do |hash, key|
|
26
|
+
# Group routes with conditionals together (`Set.new`). Like:
|
27
|
+
# get(:foobar_path, agent: /ios/) { "ios specific" }
|
28
|
+
# get(:foobar_path, agent: /android/) { "android specific" }
|
29
|
+
hash[key] = Hash.new { |h, k| h[k] = Set.new }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def route_info(controller, symbol)
|
34
|
+
methods = routes[controller][symbol].to_a
|
35
|
+
template_method = :"#{symbol}_template"
|
36
|
+
|
37
|
+
if controller.respond_to? template_method
|
38
|
+
template = controller.public_send(template_method)
|
39
|
+
else
|
40
|
+
template = symbol
|
41
|
+
symbol = '-'
|
42
|
+
end
|
43
|
+
|
44
|
+
[methods, template, symbol]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Shaf::SUPPORTED_HTTP_METHODS.each do |method|
|
50
|
+
define_method method do |path, **options, &block|
|
51
|
+
path_str = path.to_s
|
52
|
+
path_str.sub!(/_uri/, '_path')
|
53
|
+
path_str = "#{path_str}_path" unless path_str.end_with? '_path'
|
54
|
+
path_str.sub!(/_path/, '_collection_path') if options[:collection]
|
55
|
+
Registry.register(self, method, path_str.to_sym)
|
56
|
+
super(path, **options, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -8,22 +8,25 @@ module Shaf
|
|
8
8
|
|
9
9
|
attr_reader :policy_class
|
10
10
|
|
11
|
-
def authorize_with(policy_class)
|
12
|
-
@policy_class = policy_class
|
13
|
-
end
|
14
|
-
|
15
11
|
def self.registered(app)
|
16
12
|
app.helpers Helpers
|
17
13
|
end
|
14
|
+
|
15
|
+
def authorize_with(policy_class)
|
16
|
+
@policy_class = policy_class
|
17
|
+
end
|
18
18
|
end
|
19
19
|
|
20
20
|
module Helpers
|
21
21
|
def authorize(action, resource = nil)
|
22
|
-
policy(resource)
|
22
|
+
policy = policy(resource)
|
23
|
+
raise Authorize::NoPolicyError unless policy
|
24
|
+
|
23
25
|
method = __method_for(action)
|
24
|
-
return
|
26
|
+
return policy.public_send(method) if policy.respond_to? method
|
27
|
+
|
25
28
|
raise Authorize::MissingPolicyAction,
|
26
|
-
"#{
|
29
|
+
"#{policy.class} does not implement method #{method}"
|
27
30
|
end
|
28
31
|
|
29
32
|
def authorize!(action, resource = nil)
|
@@ -33,9 +36,8 @@ module Shaf
|
|
33
36
|
private
|
34
37
|
|
35
38
|
def policy(resource)
|
36
|
-
return @policy if defined?(@policy) && @policy
|
37
39
|
user = current_user if respond_to? :current_user
|
38
|
-
|
40
|
+
self.class.policy_class&.new(user, resource)
|
39
41
|
end
|
40
42
|
|
41
43
|
def __method_for(action)
|
data/lib/shaf/extensions/log.rb
CHANGED
@@ -11,15 +11,25 @@ module Shaf
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
class << self
|
15
|
+
def resource_uris_for(name, **kwargs)
|
16
|
+
CreateUriMethods.new(name, **kwargs).call
|
17
|
+
end
|
18
|
+
|
19
|
+
def register_uri(name, uri)
|
20
|
+
MethodBuilder.new(name, uri).call
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
14
24
|
def resource_uris_for(name, **kwargs)
|
15
|
-
result =
|
25
|
+
result = ResourceUris.resource_uris_for(name, **kwargs)
|
16
26
|
UriHelperMethods.add_path_helpers(self, result)
|
17
27
|
|
18
28
|
include UriHelper unless self < UriHelper
|
19
29
|
end
|
20
30
|
|
21
31
|
def register_uri(name, uri)
|
22
|
-
result =
|
32
|
+
result = ResourceUris.register_uri(name, uri)
|
23
33
|
UriHelperMethods.add_path_helpers(self, result)
|
24
34
|
|
25
35
|
include UriHelper unless self < UriHelper
|
@@ -39,23 +49,23 @@ module Shaf
|
|
39
49
|
end
|
40
50
|
|
41
51
|
def add_path_helpers(clazz, methods)
|
42
|
-
|
43
|
-
@path_helpers[clazz] ||= []
|
44
|
-
@path_helpers[clazz].concat Array(methods)
|
52
|
+
path_helpers[clazz].concat Array(methods)
|
45
53
|
end
|
46
54
|
|
47
55
|
def path_helpers_for(clazz = nil)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
56
|
+
return path_helpers unless clazz
|
57
|
+
path_helpers[clazz]
|
58
|
+
end
|
59
|
+
|
60
|
+
def path_helpers
|
61
|
+
@path_helpers ||= Hash.new { |hash, key| hash[key] = [] }
|
52
62
|
end
|
53
63
|
|
54
64
|
# For cleaning up after tests
|
55
65
|
def remove_all
|
56
66
|
helpers = instance_methods - [:path_helpers]
|
57
67
|
remove_method(*helpers)
|
58
|
-
@path_helpers = {}
|
68
|
+
@path_helpers = Hash.new { |hash, key| hash[key] = [] }
|
59
69
|
end
|
60
70
|
end
|
61
71
|
|
@@ -107,11 +117,15 @@ module Shaf
|
|
107
117
|
|
108
118
|
def call
|
109
119
|
if plural_name == name
|
120
|
+
# Deprecated code path
|
121
|
+
# Remove this branch and only keep the `else` behavior when dropping
|
122
|
+
# support for this
|
110
123
|
register_resource_helper_by_arg
|
111
124
|
else
|
112
125
|
register_collection_helper
|
113
126
|
register_resource_helper
|
114
127
|
end
|
128
|
+
|
115
129
|
register_new_resource_helper
|
116
130
|
register_edit_resource_helper
|
117
131
|
@added_path_methods
|
@@ -125,7 +139,9 @@ module Shaf
|
|
125
139
|
return if skip? :collection
|
126
140
|
|
127
141
|
template_uri = "#{base}/#{plural_name}".freeze
|
128
|
-
|
142
|
+
method_name = plural_name
|
143
|
+
method_name = "#{name}_collection" if name == @plural_name
|
144
|
+
register(method_name, template_uri)
|
129
145
|
end
|
130
146
|
|
131
147
|
def register_resource_helper
|
@@ -140,7 +156,8 @@ module Shaf
|
|
140
156
|
# as argument and the resources uri when no arguments are provided.
|
141
157
|
def register_resource_helper_by_arg
|
142
158
|
return register_resource_helper if skip? :collection
|
143
|
-
|
159
|
+
register_collection_helper
|
160
|
+
return if skip? :new
|
144
161
|
|
145
162
|
resource_template_uri = "#{base}/#{plural_name}/:id"
|
146
163
|
collection_template_uri = "#{base}/#{plural_name}"
|
@@ -180,24 +197,33 @@ module Shaf
|
|
180
197
|
end
|
181
198
|
|
182
199
|
class MethodBuilder
|
200
|
+
NO_GIVEN_VALUE = Object.new
|
201
|
+
|
183
202
|
def self.query_string(query)
|
184
|
-
return
|
185
|
-
|
203
|
+
return '' unless query&.any?
|
204
|
+
|
205
|
+
fragment_id = query.delete(:fragment_id)
|
206
|
+
fragment_str = "##{fragment_id}" if fragment_id
|
207
|
+
|
208
|
+
query_str = query.map { |a| a.join('=') }.join('&')
|
209
|
+
query_str = "?#{query_str}" unless query_str.empty?
|
210
|
+
|
211
|
+
[query_str, fragment_str].join
|
186
212
|
end
|
187
213
|
|
188
214
|
def initialize(name, uri, alt_uri: nil)
|
189
215
|
@name = name
|
190
|
-
@uri = uri
|
191
|
-
@alt_uri = alt_uri
|
216
|
+
@uri = uri.dup.freeze
|
217
|
+
@alt_uri = alt_uri.dup.freeze
|
192
218
|
end
|
193
219
|
|
194
220
|
def call
|
195
221
|
if UriHelper.respond_to? uri_method_name
|
196
222
|
exception = ResourceUris::UriHelperMethodAlreadyExistError
|
197
|
-
raise exception.new(
|
223
|
+
raise exception.new(name, uri_method_name)
|
198
224
|
end
|
199
225
|
|
200
|
-
if
|
226
|
+
if alt_uri.nil?
|
201
227
|
build_methods
|
202
228
|
else
|
203
229
|
build_methods_with_optional_arg
|
@@ -206,6 +232,8 @@ module Shaf
|
|
206
232
|
|
207
233
|
private
|
208
234
|
|
235
|
+
attr_reader :name, :uri, :alt_uri
|
236
|
+
|
209
237
|
def build_methods
|
210
238
|
UriHelperMethods.eval_method uri_method_string
|
211
239
|
UriHelperMethods.eval_method path_method_string
|
@@ -225,11 +253,11 @@ module Shaf
|
|
225
253
|
end
|
226
254
|
|
227
255
|
def uri_method_name
|
228
|
-
"#{
|
256
|
+
"#{name}_uri".freeze
|
229
257
|
end
|
230
258
|
|
231
259
|
def path_method_name
|
232
|
-
"#{
|
260
|
+
"#{name}_path".freeze
|
233
261
|
end
|
234
262
|
|
235
263
|
def path_matcher_name
|
@@ -244,81 +272,92 @@ module Shaf
|
|
244
272
|
"#{uri_method_name}_template".freeze
|
245
273
|
end
|
246
274
|
|
247
|
-
def uri_signature(optional_args: 0)
|
248
|
-
signature(uri_method_name, optional_args: optional_args)
|
275
|
+
def uri_signature(uri: @uri, optional_args: 0)
|
276
|
+
signature(uri_method_name, uri, optional_args: optional_args)
|
249
277
|
end
|
250
278
|
|
251
|
-
def path_signature(optional_args: 0)
|
252
|
-
signature(path_method_name, optional_args: optional_args)
|
279
|
+
def path_signature(uri: @uri, optional_args: 0)
|
280
|
+
signature(path_method_name, uri, optional_args: optional_args)
|
253
281
|
end
|
254
282
|
|
255
|
-
def signature(method_name, optional_args: 0)
|
256
|
-
|
283
|
+
def signature(method_name, uri, optional_args: 0)
|
284
|
+
args = extract_symbols(uri).size.times.map { |i| "arg#{i}" }
|
285
|
+
sym_count = args.size
|
257
286
|
|
258
|
-
|
259
|
-
|
287
|
+
optional_args.times { |i| args << "arg#{sym_count + i} = nil" }
|
288
|
+
args << '**query'
|
260
289
|
|
261
|
-
args
|
262
|
-
symbols.each_with_index do |arg, i|
|
263
|
-
if i < sym_count - optional_args
|
264
|
-
args << "arg#{i}"
|
265
|
-
else
|
266
|
-
args << "arg#{i} = nil"
|
267
|
-
end
|
268
|
-
end
|
269
|
-
s << (args.empty? ? "**query)" : "#{args.join(', ')}, **query)")
|
290
|
+
"#{method_name}(#{args.join(', ')})"
|
270
291
|
end
|
271
292
|
|
272
293
|
def uri_method_string
|
273
294
|
base_uri = UriHelper.base_uri
|
274
|
-
<<~
|
295
|
+
<<~RUBY
|
275
296
|
def #{uri_signature}
|
276
297
|
query_str = Shaf::MethodBuilder.query_string(query)
|
277
|
-
\"#{base_uri}#{interpolated_uri_string(
|
298
|
+
\"#{base_uri}#{interpolated_uri_string(uri)}\#{query_str}\".freeze
|
278
299
|
end
|
279
|
-
|
300
|
+
RUBY
|
280
301
|
end
|
281
302
|
|
282
303
|
def path_method_string
|
283
|
-
<<~
|
304
|
+
<<~RUBY
|
284
305
|
def #{path_signature}
|
285
306
|
query_str = Shaf::MethodBuilder.query_string(query)
|
286
|
-
\"#{interpolated_uri_string(
|
307
|
+
\"#{interpolated_uri_string(uri)}\#{query_str}\".freeze
|
287
308
|
end
|
288
|
-
|
309
|
+
RUBY
|
289
310
|
end
|
290
311
|
|
291
312
|
def uri_method_with_optional_arg_string
|
292
313
|
base_uri = UriHelper.base_uri
|
293
|
-
arg_no = extract_symbols.size
|
294
|
-
<<~
|
295
|
-
def #{uri_signature(optional_args: 1)}
|
314
|
+
arg_no = extract_symbols(alt_uri).size
|
315
|
+
<<~RUBY
|
316
|
+
def #{uri_signature(uri: alt_uri, optional_args: 1)}
|
296
317
|
query_str = Shaf::MethodBuilder.query_string(query)
|
297
318
|
if arg#{arg_no}.nil?
|
298
|
-
|
319
|
+
warn <<~DEPRECATION
|
320
|
+
|
321
|
+
Deprecated use of collection uri helper:
|
322
|
+
To get the collection uri use ##{name}_collection_uri instead of ##{uri_method_name}.
|
323
|
+
Or pass an argument to ##{uri_method_name} to get the uri to a resource.
|
324
|
+
\#{caller.find { |s| !s.match? %r{lib/shaf/extensions/resource_uris.rb} }}
|
325
|
+
|
326
|
+
DEPRECATION
|
327
|
+
|
328
|
+
\"#{base_uri}#{interpolated_uri_string(alt_uri)}\#{query_str}\".freeze
|
299
329
|
else
|
300
|
-
\"#{base_uri}#{interpolated_uri_string(
|
330
|
+
\"#{base_uri}#{interpolated_uri_string(uri)}\#{query_str}\".freeze
|
301
331
|
end
|
302
332
|
end
|
303
|
-
|
333
|
+
RUBY
|
304
334
|
end
|
305
335
|
|
306
336
|
def path_method_with_optional_arg_string
|
307
|
-
arg_no = extract_symbols.size
|
308
|
-
<<~
|
309
|
-
def #{path_signature(optional_args: 1)}
|
337
|
+
arg_no = extract_symbols(alt_uri).size
|
338
|
+
<<~RUBY
|
339
|
+
def #{path_signature(uri: alt_uri, optional_args: 1)}
|
310
340
|
query_str = Shaf::MethodBuilder.query_string(query)
|
311
341
|
if arg#{arg_no}.nil?
|
312
|
-
|
342
|
+
warn <<~DEPRECATION
|
343
|
+
|
344
|
+
Deprecated use of collection path helper:
|
345
|
+
To get the collection path use ##{name}_collection_path instead of ##{path_method_name}.
|
346
|
+
Or pass an argument to ##{path_method_name} to get the path to a resource.
|
347
|
+
\#{caller.find { |s| !s.match? %r{lib/shaf/extensions} }}
|
348
|
+
|
349
|
+
DEPRECATION
|
350
|
+
|
351
|
+
\"#{interpolated_uri_string(alt_uri)}\#{query_str}\".freeze
|
313
352
|
else
|
314
|
-
\"#{interpolated_uri_string(
|
353
|
+
\"#{interpolated_uri_string(uri)}\#{query_str}\".freeze
|
315
354
|
end
|
316
355
|
end
|
317
|
-
|
356
|
+
RUBY
|
318
357
|
end
|
319
358
|
|
320
359
|
def extract_symbols(uri = @uri)
|
321
|
-
uri.split('/').grep(
|
360
|
+
uri.split('/').grep(/\A:.+/).map { |t| t[1..-1].to_sym }
|
322
361
|
end
|
323
362
|
|
324
363
|
def transform_symbols(uri)
|
@@ -334,8 +373,13 @@ module Shaf
|
|
334
373
|
return uri if uri == '/'
|
335
374
|
|
336
375
|
transform_symbols(uri) do |segment, i|
|
337
|
-
|
338
|
-
|
376
|
+
# if the uri is templated (starting with a '{'), then we need to
|
377
|
+
# exclude it from the interpolated string but add it back to the end of
|
378
|
+
# the segment.
|
379
|
+
last = (segment.index('{') || 0) - 1
|
380
|
+
sym = segment[1..last]
|
381
|
+
template = segment[(last + 1)..-1] unless last == -1
|
382
|
+
"\#{arg#{i}.respond_to?(:#{sym}) ? arg#{i}.#{sym} : arg#{i}}#{template}"
|
339
383
|
end
|
340
384
|
end
|
341
385
|
|
@@ -343,23 +387,55 @@ module Shaf
|
|
343
387
|
uri, alt_uri = @uri, @alt_uri
|
344
388
|
|
345
389
|
if alt_uri.nil?
|
346
|
-
->
|
390
|
+
-> { uri }
|
347
391
|
else
|
348
|
-
|
392
|
+
deprecated_method = template_method_name
|
393
|
+
replacing_method = "#{name}_collection_path_template"
|
394
|
+
|
395
|
+
lambda do |collection = NO_GIVEN_VALUE|
|
396
|
+
if collection != NO_GIVEN_VALUE
|
397
|
+
warn <<~DEPRECATION
|
398
|
+
|
399
|
+
Deprecated use of uri template helper with `collection` argument:
|
400
|
+
Use #{replacing_method} instead of #{deprecated_method}"
|
401
|
+
#{caller.find { |s| !s.match? %r{lib/shaf/extensions} }}
|
402
|
+
|
403
|
+
DEPRECATION
|
404
|
+
else
|
405
|
+
collection = false
|
406
|
+
end
|
407
|
+
|
408
|
+
collection ? alt_uri : uri
|
409
|
+
end
|
349
410
|
end
|
350
411
|
end
|
351
412
|
|
352
413
|
def path_mather_patterns
|
353
414
|
[
|
354
|
-
|
355
|
-
|
415
|
+
uri.gsub(%r{:[^/]*}, '\w+'),
|
416
|
+
alt_uri&.gsub(%r{:[^/]*}, '\w+')
|
356
417
|
].compact.map { |str| Regexp.new("\\A#{str}\\Z") }
|
357
418
|
end
|
358
419
|
|
359
420
|
def path_matcher_proc
|
360
421
|
patterns = path_mather_patterns
|
361
422
|
|
362
|
-
|
423
|
+
deprecated_method = path_matcher_name
|
424
|
+
replacing_method = "#{name}_collection_path?"
|
425
|
+
|
426
|
+
lambda do |path = nil, collection: NO_GIVEN_VALUE|
|
427
|
+
if collection != NO_GIVEN_VALUE
|
428
|
+
warn <<~DEPRECATION
|
429
|
+
|
430
|
+
Deprecated use of uri predicate helper with `collection` argument:
|
431
|
+
Use #{replacing_method} instead of #{deprecated_method}(collection: true)
|
432
|
+
#{caller.find { |s| !s.match? %r{lib/shaf/extensions} }}
|
433
|
+
|
434
|
+
DEPRECATION
|
435
|
+
else
|
436
|
+
collection = false
|
437
|
+
end
|
438
|
+
|
363
439
|
unless path
|
364
440
|
r = request if respond_to? :request
|
365
441
|
path = r.path_info if r&.respond_to? :path_info
|