rest_framework 0.2.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37a40fca2be23a6fe5fb1f982349ce5dd212644ffe450d979d04c339360b22eb
4
- data.tar.gz: 3e036f7150018a46756efc378dacf16321a9394cb9d04d9b4af7220158400cda
3
+ metadata.gz: 3c2cd61ac79fb24303888d0595f53816562ee4377703dca5e6e43842db66153d
4
+ data.tar.gz: 195627f1816895b657b1e796ffb81d70781c009aa8f7266cda8a7b28cde33bc2
5
5
  SHA512:
6
- metadata.gz: 9c4a665ccde25d1c53423c168d1e5849d4ba4b91a2a0bcf6fb1f3b3714b624112d951c06aa84029aeaeb7c3703b30e8c73824688070370b3f0e9e3c3d2fd70ec
7
- data.tar.gz: 6a4511553767ed27726a9d12daf0cd46958fe2098566f0daff5c1f9056bfbfa8c26836646b76e3f4a22a1efbea341dc6cb682a04c7c1b5b7635b46cc56548c3a
6
+ metadata.gz: f3dac5f9515f8797c3d2789ccdbc2e623bafa18124b81999c1959884f14449315c574c88ff7ecdfc8766b4816067b188634d381ee2e00b9e1eff91b71a347f6f
7
+ data.tar.gz: c883ab665f21b36d7d6838ef154eec4cd2072505fc1a21626e9b56f3a4287a5d85aa5fc090d4e780259ddfe77b4993db7e44157c7aac673138f5deb24a1c438b
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/rest_framework.svg)](https://badge.fury.io/rb/rest_framework)
4
4
  [![Build Status](https://travis-ci.org/gregschmit/rails-rest-framework.svg?branch=master)](https://travis-ci.org/gregschmit/rails-rest-framework)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/gregschmit/rails-rest-framework/badge.svg?branch=master)](https://coveralls.io/github/gregschmit/rails-rest-framework?branch=master)
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ba5df7706cb544d78555/maintainability)](https://codeclimate.com/github/gregschmit/rails-rest-framework/maintainability)
6
7
 
7
8
  A framework for DRY RESTful APIs in Ruby on Rails.
8
9
 
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.3
@@ -79,8 +79,12 @@ module RESTFramework::BaseControllerMixin
79
79
 
80
80
  protected
81
81
 
82
- # Helper to get filtering backends with a sane default.
83
- # @return [RESTFramework::BaseFilter]
82
+ # Helper to get the configured serializer class.
83
+ def get_serializer_class
84
+ return self.class.serializer_class
85
+ end
86
+
87
+ # Helper to get filtering backends, defaulting to no backends.
84
88
  def get_filter_backends
85
89
  return self.class.filter_backends || []
86
90
  end
@@ -95,16 +99,10 @@ module RESTFramework::BaseControllerMixin
95
99
  return data
96
100
  end
97
101
 
98
- # Helper to get the configured serializer class.
99
- # @return [RESTFramework::BaseSerializer]
100
- def get_serializer_class
101
- return self.class.serializer_class
102
- end
103
-
104
102
  def record_invalid(e)
105
- return api_response(
106
- {message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
107
- )
103
+ return api_response({
104
+ message: "Record invalid.", exception: e, errors: e.record&.errors
105
+ }, status: 400)
108
106
  end
109
107
 
110
108
  def record_not_found(e)
@@ -112,11 +110,15 @@ module RESTFramework::BaseControllerMixin
112
110
  end
113
111
 
114
112
  def record_not_saved(e)
115
- return api_response({message: "Record not saved.", exception: e}, status: 406)
113
+ return api_response({
114
+ message: "Record not saved.", exception: e, errors: e.record&.errors
115
+ }, status: 406)
116
116
  end
117
117
 
118
118
  def record_not_destroyed(e)
119
- return api_response({message: "Record not destroyed.", exception: e}, status: 406)
119
+ return api_response({
120
+ message: "Record not destroyed.", exception: e, errors: e.record&.errors
121
+ }, status: 406)
120
122
  end
121
123
 
122
124
  # Helper for showing routes under a controller action, used for the browsable API.
@@ -124,7 +126,9 @@ module RESTFramework::BaseControllerMixin
124
126
  begin
125
127
  formatter = ActionDispatch::Routing::ConsoleFormatter::Sheet
126
128
  rescue NameError
129
+ # :nocov:
127
130
  formatter = ActionDispatch::Routing::ConsoleFormatter
131
+ # :nocov:
128
132
  end
129
133
  return ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes).format(
130
134
  formatter.new
@@ -178,9 +182,9 @@ module RESTFramework::BaseControllerMixin
178
182
  hkwargs = kwargs.merge(html_kwargs)
179
183
  begin
180
184
  render(**hkwargs)
181
- rescue ActionView::MissingTemplate # fallback to rest_framework layout/view
185
+ rescue ActionView::MissingTemplate # fallback to rest_framework layout
182
186
  hkwargs[:layout] = "rest_framework"
183
- hkwargs[:template] = "rest_framework/default"
187
+ hkwargs[:html] = ''
184
188
  render(**hkwargs)
185
189
  end
186
190
  }
@@ -20,6 +20,10 @@ module RESTFramework::BaseModelControllerMixin
20
20
  fields: nil,
21
21
  action_fields: nil,
22
22
 
23
+ # Attributes for finding records.
24
+ find_by_fields: nil,
25
+ find_by_query_param: 'find_by',
26
+
23
27
  # Attributes for create/update parameters.
24
28
  allowed_parameters: nil,
25
29
  allowed_action_parameters: nil,
@@ -33,9 +37,14 @@ module RESTFramework::BaseModelControllerMixin
33
37
  filterset_fields: nil,
34
38
  ordering_fields: nil,
35
39
  ordering_query_param: 'ordering',
40
+ ordering_no_reorder: false,
41
+ search_fields: nil,
42
+ search_query_param: 'search',
43
+ search_case_sensitive: false,
36
44
 
37
45
  # Other misc attributes.
38
- disable_creation_from_recordset: false, # Option to disable `recordset.create` behavior.
46
+ create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
47
+ filter_recordset_before_find: true, # Option to control if filtering is done before find.
39
48
  }.each do |a, default|
40
49
  unless base.respond_to?(a)
41
50
  base.class_attribute(a)
@@ -60,11 +69,6 @@ module RESTFramework::BaseModelControllerMixin
60
69
  return (action_config[action] if action) || self.class.send(generic_config_key)
61
70
  end
62
71
 
63
- # Get a list of parameters allowed for the current action.
64
- def get_allowed_parameters
65
- return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
66
- end
67
-
68
72
  # Get a list of fields for the current action.
69
73
  def get_fields
70
74
  return (
@@ -74,14 +78,37 @@ module RESTFramework::BaseModelControllerMixin
74
78
  )
75
79
  end
76
80
 
81
+ # Get a list of find_by fields for the current action.
82
+ def get_find_by_fields
83
+ return self.class.find_by_fields&.map(&:to_s) || self.get_fields
84
+ end
85
+
86
+ # Get a list of find_by fields for the current action.
87
+ def get_filterset_fields
88
+ return self.class.filterset_fields&.map(&:to_s) || self.get_fields
89
+ end
90
+
91
+ # Get a list of ordering fields for the current action.
92
+ def get_ordering_fields
93
+ return self.class.ordering_fields&.map(&:to_s) || self.get_fields
94
+ end
95
+
96
+ # Get a list of search fields for the current action.
97
+ def get_search_fields
98
+ return self.class.search_fields&.map(&:to_s) || self.get_fields
99
+ end
100
+
101
+ # Get a list of parameters allowed for the current action.
102
+ def get_allowed_parameters
103
+ return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
104
+ end
105
+
77
106
  # Helper to get the configured serializer class, or `NativeSerializer` as a default.
78
- # @return [RESTFramework::BaseSerializer]
79
107
  def get_serializer_class
80
108
  return self.class.serializer_class || RESTFramework::NativeSerializer
81
109
  end
82
110
 
83
- # Get the list of filtering backends to use.
84
- # @return [RESTFramework::BaseFilter]
111
+ # Helper to get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
85
112
  def get_filter_backends
86
113
  return self.class.filter_backends || [
87
114
  RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
@@ -94,9 +121,7 @@ module RESTFramework::BaseModelControllerMixin
94
121
  fields = self.get_allowed_parameters || self.get_fields
95
122
 
96
123
  # Filter the request body.
97
- body_params = request.request_parameters.select { |p|
98
- fields.include?(p)
99
- }
124
+ body_params = request.request_parameters.select { |p| fields.include?(p) }
100
125
 
101
126
  # Add query params in place of missing body params, if configured.
102
127
  if self.class.accept_generic_params_as_body_params
@@ -113,9 +138,7 @@ module RESTFramework::BaseModelControllerMixin
113
138
  end
114
139
 
115
140
  # Filter fields in exclude_body_fields.
116
- (self.class.exclude_body_fields || []).each do |f|
117
- body_params.delete(f.to_s)
118
- end
141
+ (self.class.exclude_body_fields || []).each { |f| body_params.delete(f.to_s) }
119
142
 
120
143
  body_params
121
144
  end
@@ -123,14 +146,6 @@ module RESTFramework::BaseModelControllerMixin
123
146
  alias :get_create_params :get_body_params
124
147
  alias :get_update_params :get_body_params
125
148
 
126
- # Get a record by the primary key from the (non-filtered) recordset.
127
- def get_record
128
- if pk = params[self.get_model.primary_key]
129
- return self.get_recordset.find(pk)
130
- end
131
- return nil
132
- end
133
-
134
149
  # Get the model for this controller.
135
150
  def get_model(from_get_recordset: false)
136
151
  return @model if instance_variable_defined?(:@model) && @model
@@ -164,6 +179,29 @@ module RESTFramework::BaseModelControllerMixin
164
179
 
165
180
  return nil
166
181
  end
182
+
183
+ # Get a single record by primary key or another column, if allowed.
184
+ def get_record
185
+ recordset = self.get_recordset
186
+ find_by_fields = self.get_find_by_fields
187
+ find_by_key = self.get_model.primary_key
188
+
189
+ # Find by another column if it's permitted.
190
+ if find_by_query_param && params[find_by_query_param].in?(find_by_fields)
191
+ find_by_key = params[find_by_query_param]
192
+ end
193
+
194
+ # Filter recordset, if configured.
195
+ if self.filter_recordset_before_find
196
+ recordset = self.get_filtered_data(recordset)
197
+ end
198
+
199
+ # Return the record.
200
+ if find_by_value = params[:id] # Route key is always :id by Rails convention.
201
+ return self.get_recordset.find_by!(find_by_key => find_by_value)
202
+ end
203
+ return nil
204
+ end
167
205
  end
168
206
 
169
207
 
@@ -200,8 +238,8 @@ end
200
238
  # Mixin for creating records.
201
239
  module RESTFramework::CreateModelMixin
202
240
  def create
203
- if self.get_recordset.respond_to?(:create!) && !self.disable_creation_from_recordset
204
- # Create with any properties inherited from the recordset (like associations).
241
+ if self.get_recordset.respond_to?(:create!) && self.create_from_recordset
242
+ # Create with any properties inherited from the recordset.
205
243
  @record = self.get_recordset.create!(self.get_create_params)
206
244
  else
207
245
  # Otherwise, perform a "bare" create.
@@ -2,6 +2,7 @@
2
2
  class RESTFramework::Error < StandardError
3
3
  end
4
4
 
5
+
5
6
  class RESTFramework::NilPassedToAPIResponseError < RESTFramework::Error
6
7
  def message
7
8
  return <<~MSG.split("\n").join(' ')
@@ -14,6 +15,7 @@ class RESTFramework::NilPassedToAPIResponseError < RESTFramework::Error
14
15
  end
15
16
  end
16
17
 
18
+
17
19
  class RESTFramework::UnserializableError < RESTFramework::Error
18
20
  def initialize(object)
19
21
  @object = object
@@ -14,8 +14,8 @@ end
14
14
  class RESTFramework::ModelFilter < RESTFramework::BaseFilter
15
15
  # Filter params for keys allowed by the current action's filterset_fields/fields config.
16
16
  def _get_filter_params
17
- fields = @controller.class.filterset_fields&.map(&:to_s) || @controller.send(:get_fields)
18
- return @controller.request.query_parameters.select { |p|
17
+ fields = @controller.send(:get_filterset_fields)
18
+ return @controller.request.query_parameters.select { |p, _|
19
19
  fields.include?(p)
20
20
  }.to_h.symbolize_keys # convert from HashWithIndifferentAccess to Hash w/keys
21
21
  end
@@ -36,17 +36,25 @@ end
36
36
  class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
37
37
  # Convert ordering string to an ordering configuration.
38
38
  def _get_ordering
39
- return nil unless @controller.class.ordering_query_param
40
-
39
+ return nil if @controller.class.ordering_query_param.blank?
40
+ ordering_fields = @controller.send(:get_ordering_fields)
41
41
  order_string = @controller.params[@controller.class.ordering_query_param]
42
+
42
43
  unless order_string.blank?
43
- return order_string.split(',').map { |field|
44
+ ordering = {}
45
+ order_string.split(',').each do |field|
44
46
  if field[0] == '-'
45
- [field[1..-1].to_sym, :desc]
47
+ column = field[1..-1]
48
+ direction = :desc
46
49
  else
47
- [field.to_sym, :asc]
50
+ column = field
51
+ direction = :asc
48
52
  end
49
- }.to_h
53
+ if column.in?(ordering_fields)
54
+ ordering[column.to_sym] = direction
55
+ end
56
+ end
57
+ return ordering
50
58
  end
51
59
 
52
60
  return nil
@@ -55,14 +63,31 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
55
63
  # Order data according to the request query parameters.
56
64
  def get_filtered_data(data)
57
65
  ordering = self._get_ordering
66
+ reorder = !@controller.send(:ordering_no_reorder)
67
+
58
68
  if ordering && !ordering.empty?
59
- return data.order(_get_ordering)
69
+ return data.send(reorder ? :reorder : :order, _get_ordering)
60
70
  end
71
+
61
72
  return data
62
73
  end
63
74
  end
64
75
 
65
76
 
66
- # TODO: implement searching within fields rather than exact match filtering (ModelFilter)
67
- # class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
68
- # end
77
+ # Multi-field text searching on models.
78
+ class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
79
+ # Filter data according to the request query parameters.
80
+ def get_filtered_data(data)
81
+ fields = @controller.send(:get_search_fields)
82
+ search = @controller.request.query_parameters[@controller.send(:search_query_param)]
83
+
84
+ # Ensure we use array conditions to prevent SQL injection.
85
+ unless search.blank?
86
+ return data.where(fields.map { |f|
87
+ "CAST(#{f} AS text) #{@controller.send(:search_case_sensitive) ? "LIKE" : "ILIKE"} ?"
88
+ }.join(' OR '), *(["%#{search}%"] * fields.length))
89
+ end
90
+
91
+ return data
92
+ end
93
+ end
@@ -1,26 +1,67 @@
1
1
  require 'rails/generators'
2
2
 
3
3
 
4
+ # Some projects don't have the inflection "REST" as an acronym, so this is a helper class to prevent
5
+ # this generator from being namespaced under `r_e_s_t_framework`.
6
+ # :nocov:
7
+ class RESTFrameworkCustomGeneratorControllerNamespace < String
8
+ def camelize
9
+ return "RESTFramework"
10
+ end
11
+ end
12
+ # :nocov:
13
+
14
+
4
15
  class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
16
+ PATH_REGEX = /^\/*([a-z0-9_\/]*[a-z0-9_])(?:[\.a-z\/]*)$/
17
+
5
18
  desc <<~END
6
- Description:
7
- Stubs out a active_scaffolded controller. Pass the model name,
8
- either CamelCased or under_scored.
9
- The controller name is retrieved as a pluralized version of the model
10
- name.
11
- To create a controller within a module, specify the model name as a
12
- path like 'parent_module/controller_name'.
13
- This generates a controller class in app/controllers and invokes helper,
14
- template engine and test framework generators.
15
- Example:
16
- `rails generate rest_framework:controller CreditCard`
17
- Credit card controller with URLs like /credit_card/debit.
18
- Controller: app/controllers/credit_cards_controller.rb
19
- Functional Test: test/functional/credit_cards_controller_test.rb
20
- Helper: app/helpers/credit_cards_helper.rb
19
+ Description:
20
+ Generates a new REST Framework controller.
21
+
22
+ Specify the controller as a path, including the module, if needed, like:
23
+ 'parent_module/controller_name'.
24
+
25
+ Example:
26
+ `rails generate rest_framework:controller user_api/groups`
27
+
28
+ Generates a controller at `app/controllers/user_api/groups_controller.rb` named
29
+ `UserApi::GroupsController`.
21
30
  END
22
31
 
23
- def create_controller_file
24
- create_file "app/controllers/some_controller.rb", "# Add initialization content here"
32
+ argument :path, type: :string
33
+ class_option(
34
+ :parent_class,
35
+ type: :string,
36
+ default: 'ApplicationController',
37
+ desc: "Inheritance parent",
38
+ )
39
+ class_option(
40
+ :include_base,
41
+ type: :boolean,
42
+ default: false,
43
+ desc: "Include `BaseControllerMixin`, not `ModelControllerMixin`",
44
+ )
45
+
46
+ # Some projects may not have the inflection "REST" as an acronym, which changes this generator to
47
+ # be namespaced in `r_e_s_t_framework`, which is weird.
48
+ def self.namespace
49
+ return RESTFrameworkCustomGeneratorControllerNamespace.new("rest_framework:controller")
50
+ end
51
+
52
+ def create_rest_controller_file
53
+ unless (path_match = PATH_REGEX.match(self.path))
54
+ raise StandardError.new("Path isn't correct.")
55
+ end
56
+
57
+ cleaned_path = path_match[1]
58
+ content = <<~END
59
+ class #{cleaned_path.camelize}Controller < #{options[:parent_class]}
60
+ include RESTFramework::#{
61
+ options[:include_base] ? "BaseControllerMixin" : "ModelControllerMixin"
62
+ }
63
+ end
64
+ END
65
+ create_file("app/controllers/#{path}_controller.rb", content)
25
66
  end
26
67
  end
@@ -83,6 +83,7 @@ class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
83
83
  return {
84
84
  count: @count,
85
85
  page: @page_number,
86
+ page_size: @page_size,
86
87
  total_pages: @total_pages,
87
88
  results: serialized_page,
88
89
  }
@@ -6,7 +6,7 @@ module ActionDispatch::Routing
6
6
  # Internal helper to take extra_actions hash and convert to a consistent format.
7
7
  protected def _parse_extra_actions(extra_actions)
8
8
  return (extra_actions || {}).map do |k,v|
9
- kwargs = {}
9
+ kwargs = {action: k}
10
10
  path = k
11
11
 
12
12
  # Convert structure to path/methods/kwargs.
@@ -25,11 +25,6 @@ module ActionDispatch::Routing
25
25
  path = v.delete(:path)
26
26
  end
27
27
 
28
- # Set the action to be the action key unless it's already defined.
29
- if !kwargs[:action]
30
- kwargs[:action] = k
31
- end
32
-
33
28
  # Pass any further kwargs to the underlying Rails interface.
34
29
  kwargs = kwargs.merge(v)
35
30
  elsif v.is_a?(Symbol) || v.is_a?(String)
@@ -77,6 +72,16 @@ module ActionDispatch::Routing
77
72
  return controller
78
73
  end
79
74
 
75
+ # Interal interface for routing extra actions.
76
+ protected def _route_extra_actions(actions, &block)
77
+ actions.each do |action_config|
78
+ action_config[:methods].each do |m|
79
+ public_send(m, action_config[:path], **action_config[:kwargs])
80
+ end
81
+ yield if block_given?
82
+ end
83
+ end
84
+
80
85
  # Internal core implementation of the `rest_resource(s)` router, both singular and plural.
81
86
  # @param default_singular [Boolean] the default plurality of the resource if the plurality is
82
87
  # not otherwise defined by the controller
@@ -94,9 +99,11 @@ module ActionDispatch::Routing
94
99
  kwargs[:controller] = name unless kwargs[:controller]
95
100
 
96
101
  # determine plural/singular resource
97
- if kwargs.delete(:force_singular)
102
+ force_singular = kwargs.delete(:force_singular)
103
+ force_plural = kwargs.delete(:force_plural)
104
+ if force_singular
98
105
  singular = true
99
- elsif kwargs.delete(:force_plural)
106
+ elsif force_plural
100
107
  singular = false
101
108
  elsif !controller_class.singleton_controller.nil?
102
109
  singular = controller_class.singleton_controller
@@ -105,28 +112,20 @@ module ActionDispatch::Routing
105
112
  end
106
113
  resource_method = singular ? :resource : :resources
107
114
 
108
- # call either `resource` or `resources`, passing appropriate modifiers
115
+ # Call either `resource` or `resources`, passing appropriate modifiers.
109
116
  skip_undefined = kwargs.delete(:skip_undefined) || true
110
117
  skip = controller_class.get_skip_actions(skip_undefined: skip_undefined)
111
118
  public_send(resource_method, name, except: skip, **kwargs) do
112
119
  if controller_class.respond_to?(:extra_member_actions)
113
120
  member do
114
121
  actions = self._parse_extra_actions(controller_class.extra_member_actions)
115
- actions.each do |action_config|
116
- action_config[:methods].each do |m|
117
- public_send(m, action_config[:path], **action_config[:kwargs])
118
- end
119
- end
122
+ _route_extra_actions(actions)
120
123
  end
121
124
  end
122
125
 
123
126
  collection do
124
127
  actions = self._parse_extra_actions(controller_class.extra_actions)
125
- actions.each do |action_config|
126
- action_config[:methods].each do |m|
127
- public_send(m, action_config[:path], **action_config[:kwargs])
128
- end
129
- end
128
+ _route_extra_actions(actions)
130
129
  end
131
130
 
132
131
  yield if block_given?
@@ -150,6 +149,7 @@ module ActionDispatch::Routing
150
149
  # Route a controller without the default resourceful paths.
151
150
  def rest_route(name=nil, **kwargs, &block)
152
151
  controller = kwargs.delete(:controller) || name
152
+ route_root_to = kwargs.delete(:route_root_to)
153
153
  if controller.is_a?(Class)
154
154
  controller_class = controller
155
155
  else
@@ -162,42 +162,27 @@ module ActionDispatch::Routing
162
162
  # Route actions using the resourceful router, but skip all builtin actions.
163
163
  actions = self._parse_extra_actions(controller_class.extra_actions)
164
164
  public_send(:resource, name, only: [], **kwargs) do
165
- actions.each do |action_config|
166
- action_config[:methods].each do |m|
167
- public_send(m, action_config[:path], **action_config[:kwargs])
168
- end
169
- yield if block_given?
165
+ # Route a root for this resource.
166
+ if route_root_to
167
+ get '', action: route_root_to
170
168
  end
169
+
170
+ _route_extra_actions(actions, &block)
171
171
  end
172
172
  end
173
173
 
174
174
  # Route a controller's `#root` to '/' in the current scope/namespace, along with other actions.
175
- # @param name [Symbol] the snake_case name of the controller
176
175
  def rest_root(name=nil, **kwargs, &block)
177
176
  # By default, use RootController#root.
178
177
  root_action = kwargs.delete(:action) || :root
179
178
  controller = kwargs.delete(:controller) || name || :root
180
179
 
181
- # Route the root.
182
- get name.to_s, controller: controller, action: root_action
183
-
184
- # Route any additional actions.
185
- controller_class = self._get_controller_class(controller, pluralize: false)
186
- actions = self._parse_extra_actions(controller_class.extra_actions)
187
- actions.each do |action_config|
188
- # Add :action unless kwargs defines it.
189
- unless action_config[:kwargs].key?(:action)
190
- action_config[:kwargs][:action] = action_config[:path]
191
- end
180
+ # Remove path if name is nil (routing to the root of current namespace).
181
+ unless name
182
+ kwargs[:path] = ''
183
+ end
192
184
 
193
- action_config[:methods].each do |m|
194
- public_send(
195
- m,
196
- File.join(name.to_s, action_config[:path].to_s),
197
- controller: controller,
198
- **action_config[:kwargs],
199
- )
200
- end
185
+ return rest_route(controller, route_root_to: root_action, **kwargs) do
201
186
  yield if block_given?
202
187
  end
203
188
  end
@@ -121,29 +121,21 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
121
121
 
122
122
  # Allow a serializer instance to be used as a hash directly in a nested serializer config.
123
123
  def [](key)
124
- unless instance_variable_defined?(:@_nested_config)
125
- @_nested_config = self.get_serializer_config
126
- end
124
+ @_nested_config ||= self.get_serializer_config
127
125
  return @_nested_config[key]
128
126
  end
129
127
  def []=(key, value)
130
- unless instance_variable_defined?(:@_nested_config)
131
- @_nested_config = self.get_serializer_config
132
- end
128
+ @_nested_config ||= self.get_serializer_config
133
129
  return @_nested_config[key] = value
134
130
  end
135
131
 
136
132
  # Allow a serializer class to be used as a hash directly in a nested serializer config.
137
133
  def self.[](key)
138
- unless instance_variable_defined?(:@_nested_config)
139
- @_nested_config = self.new.get_serializer_config
140
- end
134
+ @_nested_config ||= self.new.get_serializer_config
141
135
  return @_nested_config[key]
142
136
  end
143
137
  def self.[]=(key, value)
144
- unless instance_variable_defined?(:@_nested_config)
145
- @_nested_config = self.new.get_serializer_config
146
- end
138
+ @_nested_config ||= self.new.get_serializer_config
147
139
  return @_nested_config[key] = value
148
140
  end
149
141
  end
@@ -1,35 +1,32 @@
1
+ # Do not use Rails-specific helper methods here (e.g., `blank?`) so the module can run standalone.
1
2
  module RESTFramework
2
3
  module Version
3
- @_version = nil
4
+ VERSION_FILEPATH = File.expand_path("../../VERSION", __dir__)
4
5
 
5
6
  def self.get_version(skip_git: false)
6
- # Return cached @_version, if available.
7
- return @_version if @_version
8
-
9
7
  # First, attempt to get the version from git.
10
8
  unless skip_git
11
- begin
12
- version = `git describe 2>/dev/null`.strip
13
- raise "blank version" if version.nil? || version.match(/^\w*$/)
14
- # Check for local changes.
15
- changes = `git status --porcelain 2>/dev/null`
16
- version << '.localchanges' if changes.strip.length > 0
17
- return version
18
- rescue
19
- end
9
+ version = `git describe --dirty --broken 2>/dev/null`&.strip
10
+ return version unless !version || version.empty?
20
11
  end
21
12
 
22
- # Git failed, so try to find a VERSION_STAMP.
13
+ # Git failed or was skipped, so try to find a VERSION file.
23
14
  begin
24
- version = File.read(File.expand_path("VERSION_STAMP", __dir__))
25
- unless version.nil? || version.match(/^\w*$/)
26
- return (@_version = version) # cache VERSION_STAMP content
27
- end
28
- rescue
15
+ version = File.read(VERSION_FILEPATH)&.strip
16
+ return version unless !version || version.blank?
17
+ rescue SystemCallError
29
18
  end
30
19
 
31
- # No VERSION_STAMP, so version is unknown.
32
- return '0.unknown'
20
+ # No VERSION file, so version is unknown.
21
+ return 'unknown'
22
+ end
23
+
24
+ def self.stamp_version
25
+ File.write(VERSION_FILEPATH, RESTFramework::VERSION)
26
+ end
27
+
28
+ def self.unstamp_version
29
+ File.delete(VERSION_FILEPATH) if File.exist?(VERSION_FILEPATH)
33
30
  end
34
31
  end
35
32
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregory N. Schmit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-28 00:00:00.000000000 Z
11
+ date: 2021-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -33,12 +33,11 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - LICENSE
35
35
  - README.md
36
+ - VERSION
36
37
  - app/views/layouts/rest_framework.html.erb
37
38
  - app/views/rest_framework/_head.html.erb
38
39
  - app/views/rest_framework/_routes.html.erb
39
- - app/views/rest_framework/default.html.erb
40
40
  - lib/rest_framework.rb
41
- - lib/rest_framework/VERSION_STAMP
42
41
  - lib/rest_framework/controller_mixins.rb
43
42
  - lib/rest_framework/controller_mixins/base.rb
44
43
  - lib/rest_framework/controller_mixins/models.rb
@@ -73,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
72
  - !ruby/object:Gem::Version
74
73
  version: '0'
75
74
  requirements: []
76
- rubygems_version: 3.0.9
75
+ rubygems_version: 3.1.4
77
76
  signing_key:
78
77
  specification_version: 4
79
78
  summary: A framework for DRY RESTful APIs in Ruby on Rails.
File without changes
@@ -1 +0,0 @@
1
- 0.2.2