shaf 1.6.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/iana_link_relations.csv.gz +0 -0
  4. data/lib/shaf/alps/attribute_serializer.rb +41 -0
  5. data/lib/shaf/alps/json_serializer.rb +50 -0
  6. data/lib/shaf/alps/relation_serializer.rb +70 -0
  7. data/lib/shaf/app.rb +32 -7
  8. data/lib/shaf/authenticator/base.rb +161 -0
  9. data/lib/shaf/authenticator/basic_auth.rb +25 -0
  10. data/lib/shaf/authenticator/challenge.rb +32 -0
  11. data/lib/shaf/authenticator/parameter.rb +31 -0
  12. data/lib/shaf/authenticator/request.rb +17 -0
  13. data/lib/shaf/authenticator.rb +56 -0
  14. data/lib/shaf/command/base.rb +4 -0
  15. data/lib/shaf/command/console.rb +1 -1
  16. data/lib/shaf/command/generate.rb +5 -2
  17. data/lib/shaf/command/new.rb +24 -7
  18. data/lib/shaf/command/server.rb +5 -1
  19. data/lib/shaf/command/templates/Gemfile.erb +1 -0
  20. data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +9 -12
  21. data/lib/shaf/errors.rb +11 -0
  22. data/lib/shaf/extensions/api_routes.rb +60 -0
  23. data/lib/shaf/extensions/authorize.rb +11 -9
  24. data/lib/shaf/extensions/log.rb +1 -1
  25. data/lib/shaf/extensions/resource_uris.rb +95 -137
  26. data/lib/shaf/extensions/symbolic_routes.rb +9 -19
  27. data/lib/shaf/extensions.rb +3 -3
  28. data/lib/shaf/formable/builder.rb +58 -19
  29. data/lib/shaf/formable/form.rb +14 -11
  30. data/lib/shaf/formable.rb +10 -24
  31. data/lib/shaf/generator/base.rb +84 -3
  32. data/lib/shaf/generator/controller.rb +30 -42
  33. data/lib/shaf/generator/doc.rb +17 -0
  34. data/lib/shaf/generator/forms.rb +11 -14
  35. data/lib/shaf/generator/helper.rb +2 -1
  36. data/lib/shaf/generator/migration/add_column.rb +0 -4
  37. data/lib/shaf/generator/migration/add_index.rb +0 -4
  38. data/lib/shaf/generator/migration/base.rb +15 -3
  39. data/lib/shaf/generator/migration/create_table.rb +0 -4
  40. data/lib/shaf/generator/migration/drop_column.rb +0 -4
  41. data/lib/shaf/generator/migration/rename_column.rb +0 -4
  42. data/lib/shaf/generator/migration/type.rb +4 -26
  43. data/lib/shaf/generator/migration/types.rb +45 -16
  44. data/lib/shaf/generator/model.rb +29 -15
  45. data/lib/shaf/generator/policy.rb +8 -14
  46. data/lib/shaf/generator/profile.rb +47 -0
  47. data/lib/shaf/generator/scaffold.rb +6 -9
  48. data/lib/shaf/generator/serializer.rb +64 -98
  49. data/lib/shaf/generator/templates/api/controller.rb.erb +13 -13
  50. data/lib/shaf/generator/templates/api/forms.rb.erb +2 -2
  51. data/lib/shaf/generator/templates/api/model.rb.erb +1 -1
  52. data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
  53. data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
  54. data/lib/shaf/generator/templates/api/serializer.rb.erb +3 -3
  55. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +15 -16
  56. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
  57. data/lib/shaf/generator.rb +2 -0
  58. data/lib/shaf/helpers/authentication.rb +79 -0
  59. data/lib/shaf/helpers/paginate.rb +1 -1
  60. data/lib/shaf/helpers/payload.rb +14 -34
  61. data/lib/shaf/helpers/vary.rb +8 -0
  62. data/lib/shaf/helpers.rb +4 -0
  63. data/lib/shaf/logger.rb +12 -0
  64. data/lib/shaf/parser/base.rb +44 -0
  65. data/lib/shaf/parser/form_data.rb +15 -0
  66. data/lib/shaf/parser/json.rb +26 -0
  67. data/lib/shaf/parser.rb +65 -0
  68. data/lib/shaf/profile/attribute.rb +29 -0
  69. data/lib/shaf/profile/evaluator.rb +46 -0
  70. data/lib/shaf/profile/relation.rb +29 -0
  71. data/lib/shaf/profile/unique_id.rb +58 -0
  72. data/lib/shaf/profile.rb +115 -0
  73. data/lib/shaf/profiles/shaf_basic.rb +20 -0
  74. data/lib/shaf/profiles/shaf_error.rb +49 -0
  75. data/lib/shaf/profiles/shaf_form.rb +110 -0
  76. data/lib/shaf/profiles.rb +46 -0
  77. data/lib/shaf/registrable_factory.rb +62 -32
  78. data/lib/shaf/responder/alps_json.rb +25 -0
  79. data/lib/shaf/responder/base.rb +20 -17
  80. data/lib/shaf/responder/hal.rb +66 -5
  81. data/lib/shaf/responder/html.rb +43 -16
  82. data/lib/shaf/responder/problem_json.rb +2 -2
  83. data/lib/shaf/responder.rb +41 -2
  84. data/lib/shaf/router.rb +65 -12
  85. data/lib/shaf/serializer.rb +35 -0
  86. data/lib/shaf/settings.rb +25 -12
  87. data/lib/shaf/spec/authenticator.rb +13 -0
  88. data/lib/shaf/spec/base.rb +1 -1
  89. data/lib/shaf/spec/http_method_utils.rb +1 -1
  90. data/lib/shaf/spec/integration_spec.rb +26 -14
  91. data/lib/shaf/spec/payload_utils.rb +2 -2
  92. data/lib/shaf/spec.rb +1 -0
  93. data/lib/shaf/supported_http_methods.rb +15 -0
  94. data/lib/shaf/tasks/routes_task.rb +14 -17
  95. data/lib/shaf/tasks.rb +0 -1
  96. data/lib/shaf/upgrade/manifest.rb +11 -2
  97. data/lib/shaf/upgrade/package.rb +73 -45
  98. data/lib/shaf/upgrade/version.rb +11 -10
  99. data/lib/shaf/utils.rb +19 -5
  100. data/lib/shaf/version.rb +3 -1
  101. data/lib/shaf/yard/attribute_method_handler.rb +19 -0
  102. data/lib/shaf/yard/attribute_object.rb +30 -0
  103. data/lib/shaf/yard/base_method_handler.rb +30 -0
  104. data/lib/shaf/yard/link_method_handler.rb +39 -0
  105. data/lib/shaf/yard/link_object.rb +60 -0
  106. data/lib/shaf/yard/nested_attributes.rb +37 -0
  107. data/lib/shaf/yard/parser.rb +64 -0
  108. data/lib/shaf/yard/profile_method_handler.rb +51 -0
  109. data/lib/shaf/yard/profile_object.rb +21 -0
  110. data/lib/shaf/yard/resource_object.rb +55 -0
  111. data/lib/shaf/yard/serializer_handler.rb +27 -0
  112. data/lib/shaf/yard.rb +34 -0
  113. data/lib/shaf.rb +6 -0
  114. data/templates/Rakefile +0 -6
  115. data/templates/api/controllers/base_controller.rb +0 -12
  116. data/templates/api/controllers/docs_controller.rb +5 -3
  117. data/templates/api/controllers/root_controller.rb +7 -1
  118. data/templates/api/policies/base_policy.rb +2 -0
  119. data/templates/api/serializers/base_serializer.rb +1 -3
  120. data/templates/api/serializers/error_serializer.rb +1 -5
  121. data/templates/api/serializers/form_serializer.rb +1 -5
  122. data/templates/api/serializers/root_serializer.rb +0 -11
  123. data/templates/api/serializers/validation_error_serializer.rb +1 -5
  124. data/templates/config/bootstrap.rb +1 -2
  125. data/templates/config/directories.rb +52 -44
  126. data/templates/config/helpers.rb +1 -1
  127. data/templates/config/initializers/authentication.rb +18 -0
  128. data/templates/config/initializers/db_migrations.rb +2 -2
  129. data/templates/config/initializers/logging.rb +2 -2
  130. data/templates/config/initializers/middleware.rb +6 -0
  131. data/templates/config/initializers.rb +52 -8
  132. data/templates/config.ru +1 -1
  133. data/templates/spec/spec_helper.rb +5 -0
  134. data/upgrades/0.5.0.tar.gz +0 -0
  135. data/upgrades/1.0.4.tar.gz +0 -0
  136. data/upgrades/1.1.0.tar.gz +0 -0
  137. data/upgrades/1.6.1.tar.gz +0 -0
  138. data/upgrades/2.0.0.tar.gz +0 -0
  139. data/upgrades/3.0.0.tar.gz +0 -0
  140. data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
  141. data/yard_templates/api_doc/doc_index/setup.rb +8 -0
  142. data/yard_templates/api_doc/html/css/api-doc.css +222 -0
  143. data/yard_templates/api_doc/html/favicon.ico +0 -0
  144. data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
  145. data/yard_templates/api_doc/html/setup.rb +59 -0
  146. data/yard_templates/api_doc/layout/html/footer.erb +3 -0
  147. data/yard_templates/api_doc/layout/html/header.erb +7 -0
  148. data/yard_templates/api_doc/layout/html/layout.erb +13 -0
  149. data/yard_templates/api_doc/layout/setup.rb +24 -0
  150. data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
  151. data/yard_templates/api_doc/profile/html/profile.erb +6 -0
  152. data/yard_templates/api_doc/profile/html/relations.erb +10 -0
  153. data/yard_templates/api_doc/profile/setup.rb +38 -0
  154. data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
  155. data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
  156. data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
  157. data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
  158. data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
  159. data/yard_templates/api_doc/resource/html/profile.erb +14 -0
  160. data/yard_templates/api_doc/resource/html/relations.erb +10 -0
  161. data/yard_templates/api_doc/resource/html/resource.erb +5 -0
  162. data/yard_templates/api_doc/resource/setup.rb +56 -0
  163. data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
  164. data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
  165. data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
  166. data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
  167. data/yard_templates/api_doc/setup.rb +31 -0
  168. data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
  169. data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
  170. data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
  171. data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
  172. data/yard_templates/api_doc/sidebar/setup.rb +56 -0
  173. data.tar.gz.sig +2 -2
  174. metadata +143 -38
  175. metadata.gz.sig +0 -0
  176. data/lib/shaf/api_doc/comment.rb +0 -27
  177. data/lib/shaf/api_doc/document.rb +0 -137
  178. data/lib/shaf/extensions/current_user.rb +0 -48
  179. data/lib/shaf/middleware.rb +0 -1
  180. data/lib/shaf/responder/hal_serializable.rb +0 -64
  181. data/lib/shaf/tasks/api_doc_task.rb +0 -125
@@ -1,27 +1,24 @@
1
1
  ---
2
2
  default: &default
3
- public_folder: frontend/assets
4
- views_folder: frontend/views
3
+ default_authentication_realm: api
5
4
  documents_dir: doc/api
6
- migrations_dir: db/migrations
7
5
  fixtures_dir: spec/fixtures
8
- paginate_per_page: 25
6
+ hostname: localhost
9
7
  http_cache: on
10
8
  http_cache_max_age_long: 86400 # 60 * 60 * 24 = 1 day
11
9
  http_cache_max_age_short: 3600 # 60 * 60 = 1 hour
12
- hostname: localhost
10
+ migrations_dir: db/migrations
11
+ paginate_per_page: 25
12
+ port: <%= default_port %>
13
+ project_name: <%= project_name %>
13
14
  protocol: http
14
- 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
15
+ public_folder: frontend/assets
16
+ views_folder: frontend/views
20
17
 
21
18
  production:
22
19
  <<: *default
23
- port: <%= ENV.fetch('PORT', 443) %>
24
20
  base_uri: https://my.public.shaf.api.com
21
+ port: <%= ENV.fetch('PORT', 443) %>
25
22
 
26
23
  development:
27
24
  <<: *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
@@ -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) or raise Authorize::NoPolicyError
22
+ policy = policy(resource)
23
+ raise Authorize::NoPolicyError unless policy
24
+
23
25
  method = __method_for(action)
24
- return @policy.public_send(method) if @policy.respond_to? method
26
+ return policy.public_send(method) if policy.respond_to? method
27
+
25
28
  raise Authorize::MissingPolicyAction,
26
- "#{@policy.class} does not implement method #{method}"
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
- @policy = self.class.policy_class&.new(user, resource)
40
+ self.class.policy_class&.new(user, resource)
39
41
  end
40
42
 
41
43
  def __method_for(action)
@@ -7,7 +7,7 @@ module Shaf
7
7
  end
8
8
 
9
9
  def log
10
- $logger ||= Logger.new('/dev/nul')
10
+ Shaf.log
11
11
  end
12
12
  end
13
13
 
@@ -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 = CreateUriMethods.new(name, **kwargs).call
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 = MethodBuilder.new(name, uri).call
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
- @path_helpers ||= {}
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
- @path_helpers ||= {}
49
- return @path_helpers unless clazz
50
- return [] unless @path_helpers.key?(clazz)
51
- @path_helpers[clazz] ||= []
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
 
@@ -96,22 +106,20 @@ module Shaf
96
106
  # edit_book_uri_template => /books/:id/edit
97
107
  #
98
108
  class CreateUriMethods
99
- def initialize(name, base: nil, plural_name: nil, only: nil, except: nil)
109
+ def initialize(name, plural_name: nil, base: nil, namespace: nil, only: nil, except: nil)
100
110
  @name = name.to_s
101
- @base = base&.sub(%r(/\Z), '') || ''
102
111
  @plural_name = plural_name&.to_s || Utils::pluralize(name.to_s)
112
+ @base = base&.sub(%r(/\Z), '') || ''
113
+ @base = "/#{namespace}" if namespace && !base
114
+ @namespace = namespace
103
115
  @only = only
104
116
  @except = except
105
117
  @added_path_methods = []
106
118
  end
107
119
 
108
120
  def call
109
- if plural_name == name
110
- register_resource_helper_by_arg
111
- else
112
- register_collection_helper
113
- register_resource_helper
114
- end
121
+ register_collection_helper
122
+ register_resource_helper
115
123
  register_new_resource_helper
116
124
  register_edit_resource_helper
117
125
  @added_path_methods
@@ -119,48 +127,38 @@ module Shaf
119
127
 
120
128
  private
121
129
 
122
- attr_reader :name, :base, :plural_name, :only, :except
130
+ attr_reader :name, :plural_name, :base, :namespace, :only, :except
123
131
 
124
132
  def register_collection_helper
125
133
  return if skip? :collection
126
134
 
135
+ method = method_name(plural_name, name == plural_name)
127
136
  template_uri = "#{base}/#{plural_name}".freeze
128
- register(plural_name, template_uri)
137
+ register(method, template_uri)
129
138
  end
130
139
 
131
140
  def register_resource_helper
132
141
  return if skip? :resource
133
142
 
143
+ method = method_name(name)
134
144
  template_uri = "#{base}/#{plural_name}/:id".freeze
135
- register(name, template_uri)
136
- end
137
-
138
- # If a resource has the same singular and plural names, then this method
139
- # should be used. It will return the resource uri when a resource is given
140
- # as argument and the resources uri when no arguments are provided.
141
- def register_resource_helper_by_arg
142
- return register_resource_helper if skip? :collection
143
- return register_collection_helper if skip? :new
144
-
145
- resource_template_uri = "#{base}/#{plural_name}/:id"
146
- collection_template_uri = "#{base}/#{plural_name}"
147
-
148
- builder = MethodBuilder.new(name, resource_template_uri, alt_uri: collection_template_uri)
149
- @added_path_methods << builder.call
145
+ register(method, template_uri)
150
146
  end
151
147
 
152
148
  def register_new_resource_helper
153
149
  return if skip? :new
154
150
 
151
+ method = method_name(name)
155
152
  template_uri = "#{base}/#{name}/form".freeze
156
- register("new_#{name}", template_uri)
153
+ register("new_#{method}", template_uri)
157
154
  end
158
155
 
159
156
  def register_edit_resource_helper
160
157
  return if skip? :edit
161
158
 
159
+ method = method_name(name)
162
160
  template_uri = "#{base}/#{plural_name}/:id/edit".freeze
163
- register("edit_#{name}", template_uri)
161
+ register("edit_#{method}", template_uri)
164
162
  end
165
163
 
166
164
  def register(name, template_uri)
@@ -177,35 +175,44 @@ module Shaf
177
175
  false
178
176
  end
179
177
  end
178
+
179
+ def method_name(name, collection = false)
180
+ collection_str = "collection" if collection
181
+ [namespace, name, collection_str].compact.join('_')
182
+ end
180
183
  end
181
184
 
182
185
  class MethodBuilder
183
186
  def self.query_string(query)
184
- return "" unless query.any?
185
- "?#{query.map { |key,value| "#{key}=#{value}" }.join("&")}"
187
+ return '' unless query&.any?
188
+
189
+ fragment_id = query.delete(:fragment_id)
190
+ fragment_str = "##{fragment_id}" if fragment_id
191
+
192
+ query_str = query.map { |a| a.join('=') }.join('&')
193
+ query_str = "?#{query_str}" unless query_str.empty?
194
+
195
+ [query_str, fragment_str].join
186
196
  end
187
197
 
188
- def initialize(name, uri, alt_uri: nil)
198
+ def initialize(name, uri)
189
199
  @name = name
190
- @uri = uri
191
- @alt_uri = alt_uri
200
+ @uri = uri.dup.freeze
192
201
  end
193
202
 
194
203
  def call
195
204
  if UriHelper.respond_to? uri_method_name
196
205
  exception = ResourceUris::UriHelperMethodAlreadyExistError
197
- raise exception.new(@name, uri_method_name)
206
+ raise exception.new(name, uri_method_name)
198
207
  end
199
208
 
200
- if @alt_uri.nil?
201
- build_methods
202
- else
203
- build_methods_with_optional_arg
204
- end
209
+ build_methods
205
210
  end
206
211
 
207
212
  private
208
213
 
214
+ attr_reader :name, :uri
215
+
209
216
  def build_methods
210
217
  UriHelperMethods.eval_method uri_method_string
211
218
  UriHelperMethods.eval_method path_method_string
@@ -215,21 +222,12 @@ module Shaf
215
222
  path_method_name.to_sym
216
223
  end
217
224
 
218
- def build_methods_with_optional_arg
219
- UriHelperMethods.eval_method uri_method_with_optional_arg_string
220
- UriHelperMethods.eval_method path_method_with_optional_arg_string
221
- UriHelperMethods.register(template_method_name, &template_proc)
222
- UriHelperMethods.register(legacy_template_method_name, &template_proc)
223
- UriHelperMethods.register(path_matcher_name, &path_matcher_proc)
224
- path_method_name.to_sym
225
- end
226
-
227
225
  def uri_method_name
228
- "#{@name}_uri".freeze
226
+ "#{name}_uri".freeze
229
227
  end
230
228
 
231
229
  def path_method_name
232
- "#{@name}_path".freeze
230
+ "#{name}_path".freeze
233
231
  end
234
232
 
235
233
  def path_matcher_name
@@ -244,81 +242,45 @@ module Shaf
244
242
  "#{uri_method_name}_template".freeze
245
243
  end
246
244
 
247
- def uri_signature(optional_args: 0)
248
- signature(uri_method_name, optional_args: optional_args)
245
+ def uri_signature(uri: @uri, optional_args: 0)
246
+ signature(uri_method_name, uri, optional_args: optional_args)
249
247
  end
250
248
 
251
- def path_signature(optional_args: 0)
252
- signature(path_method_name, optional_args: optional_args)
249
+ def path_signature(uri: @uri, optional_args: 0)
250
+ signature(path_method_name, uri, optional_args: optional_args)
253
251
  end
254
252
 
255
- def signature(method_name, optional_args: 0)
256
- s = "#{method_name}("
253
+ def signature(method_name, uri, optional_args: 0)
254
+ args = extract_symbols(uri).size.times.map { |i| "arg#{i}" }
255
+ sym_count = args.size
257
256
 
258
- symbols = extract_symbols.size.times.map { |i| "arg#{i}" }
259
- sym_count = symbols.size
257
+ optional_args.times { |i| args << "arg#{sym_count + i} = nil" }
258
+ args << '**query'
260
259
 
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)")
260
+ "#{method_name}(#{args.join(', ')})"
270
261
  end
271
262
 
272
263
  def uri_method_string
273
264
  base_uri = UriHelper.base_uri
274
- <<~Ruby
265
+ <<~RUBY
275
266
  def #{uri_signature}
276
267
  query_str = Shaf::MethodBuilder.query_string(query)
277
- \"#{base_uri}#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
268
+ \"#{base_uri}#{interpolated_uri_string(uri)}\#{query_str}\".freeze
278
269
  end
279
- Ruby
270
+ RUBY
280
271
  end
281
272
 
282
273
  def path_method_string
283
- <<~Ruby
274
+ <<~RUBY
284
275
  def #{path_signature}
285
276
  query_str = Shaf::MethodBuilder.query_string(query)
286
- \"#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
277
+ \"#{interpolated_uri_string(uri)}\#{query_str}\".freeze
287
278
  end
288
- Ruby
289
- end
290
-
291
- def uri_method_with_optional_arg_string
292
- base_uri = UriHelper.base_uri
293
- arg_no = extract_symbols.size - 1
294
- <<~Ruby
295
- def #{uri_signature(optional_args: 1)}
296
- query_str = Shaf::MethodBuilder.query_string(query)
297
- if arg#{arg_no}.nil?
298
- \"#{base_uri}#{interpolated_uri_string(@alt_uri)}\#{query_str}\".freeze
299
- else
300
- \"#{base_uri}#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
301
- end
302
- end
303
- Ruby
304
- end
305
-
306
- def path_method_with_optional_arg_string
307
- arg_no = extract_symbols.size - 1
308
- <<~Ruby
309
- def #{path_signature(optional_args: 1)}
310
- query_str = Shaf::MethodBuilder.query_string(query)
311
- if arg#{arg_no}.nil?
312
- \"#{interpolated_uri_string(@alt_uri)}\#{query_str}\".freeze
313
- else
314
- \"#{interpolated_uri_string(@uri)}\#{query_str}\".freeze
315
- end
316
- end
317
- Ruby
279
+ RUBY
318
280
  end
319
281
 
320
282
  def extract_symbols(uri = @uri)
321
- uri.split('/').grep(/:.*/).map { |t| t[1..-1].to_sym }
283
+ uri.split('/').grep(/\A:.+/).map { |t| t[1..-1].to_sym }
322
284
  end
323
285
 
324
286
  def transform_symbols(uri)
@@ -334,44 +296,40 @@ module Shaf
334
296
  return uri if uri == '/'
335
297
 
336
298
  transform_symbols(uri) do |segment, i|
337
- sym = segment[1..-1]
338
- "\#{arg#{i}.respond_to?(#{segment}) ? arg#{i}.#{sym} : arg#{i}}"
299
+ # if the uri is templated (starting with a '{'), then we need to
300
+ # exclude it from the interpolated string but add it back to the end of
301
+ # the segment.
302
+ last = (segment.index('{') || 0) - 1
303
+ sym = segment[1..last]
304
+ template = segment[(last + 1)..-1] unless last == -1
305
+ "\#{arg#{i}.respond_to?(:#{sym}) ? arg#{i}.#{sym} : arg#{i}}#{template}"
339
306
  end
340
307
  end
341
308
 
342
309
  def template_proc
343
- uri, alt_uri = @uri, @alt_uri
344
-
345
- if alt_uri.nil?
346
- ->(_ = nil) { uri.freeze }
347
- else
348
- ->(collection = false) { collection ? alt_uri : uri }
349
- end
310
+ uri = @uri
311
+ -> { uri }
350
312
  end
351
313
 
352
- def path_mather_patterns
353
- [
354
- @uri.gsub(%r{:[^/]*}, '\w+'),
355
- @alt_uri&.gsub(%r{:[^/]*}, '\w+')
356
- ].compact.map { |str| Regexp.new("\\A#{str}\\Z") }
314
+ def path_mather_pattern
315
+ pattern = uri.gsub(%r{:[^/]*}, '\w+')
316
+ Regexp.new("\\A#{pattern}\\Z")
357
317
  end
358
318
 
359
319
  def path_matcher_proc
360
- patterns = path_mather_patterns
320
+ pattern = path_mather_pattern
361
321
 
362
- lambda do |path = nil, collection: false|
322
+ lambda do |path = nil|
363
323
  unless path
364
324
  r = request if respond_to? :request
365
- path = r.path_info if r&.respond_to? :path_info
366
-
367
- unless path
368
- raise(
369
- ArgumentError,
370
- "Uri must be given (or #{self} should respond to :request)"
371
- )
372
- end
325
+ path = r.path_info if r.respond_to? :path_info
326
+
327
+ raise(
328
+ ArgumentError,
329
+ "Uri must be given (or #{self} should respond to :request)"
330
+ ) unless path
373
331
  end
374
- pattern = collection ? patterns.last : patterns.first
332
+
375
333
  !!(pattern =~ path)
376
334
  end
377
335
  end
@@ -2,32 +2,22 @@ module Shaf
2
2
  module SymbolicRoutes
3
3
  class UriHelperNotRegisterdError < Error; end
4
4
 
5
- SUPPORTED_METHODS = [
6
- :get,
7
- :put,
8
- :post,
9
- :patch,
10
- :delete,
11
- :head,
12
- :options,
13
- :link,
14
- :unlink
15
- ].freeze
16
-
17
- SUPPORTED_METHODS.each do |m|
18
- define_method m do |path, collection: false, &block|
19
- super(rewrite_path(path, collection), &block)
5
+ Shaf::SUPPORTED_HTTP_METHODS.each do |m|
6
+ define_method m do |path, **options, &block|
7
+ path = rewrite_path(path, m)
8
+ super(path, **options, &block)
20
9
  end
21
10
  end
22
11
 
23
- def rewrite_path(path, collection = nil)
12
+ def rewrite_path(path, method)
24
13
  return path unless path.is_a? Symbol
25
14
 
26
- m = "#{path}_template"
27
- return send(m, collection) if respond_to? m
15
+ ["#{path}_template", "#{path}_path_template"].each do |method|
16
+ return send(method) if respond_to? method
17
+ end
28
18
 
29
19
  raise UriHelperNotRegisterdError, <<~RUBY
30
- Undefined method '#{m}'. Did you forget to register a uri helper for #{path}?
20
+ Undefined method '#{method}'. Did you forget to register a uri helper for #{path}?
31
21
  RUBY
32
22
  end
33
23
  end
@@ -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