rest_framework 0.2.1 → 0.3.2

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: 3af970dedaa74cde70348a09e74b190878e4a429a4db0318b093c581f810a349
4
- data.tar.gz: 5ccd0cf76d51d9d5d3035eee80e73e1234b13c974d04bbd4e80a48bc5b82d714
3
+ metadata.gz: cb9085823972b6991c739761eb1ec47ce012e9b0c40e0215199b1cc1f2e681c0
4
+ data.tar.gz: 7c211b54982077d586d35cd0c04401f1e7ec54cd5363234f5832651f605aee5d
5
5
  SHA512:
6
- metadata.gz: a5841e302bdf9f2ae56748c2c81e94338a9c77c0dde248218d48733ff9bda28dc4a42e9cb5b21c339abf596490bb6de21e18a1f43bb8ef80802ce8d8db4d925e
7
- data.tar.gz: 0b395e37ac08c45e3bf8d3940dde55fba063cb43b3e8125e4c9a55dbf6dc15e985c1fa72e51a042e4563194b0f4fa5f33dd3fa5e96de456eaf9448a27b7f76ad
6
+ metadata.gz: a92b41406fa3d2723fe29e53ff38c129453b3357e6d21f51fb79de797812a2d20ea0dd5936ace48834084d00887658daf7be63bbb65e98d7cb7eee21fbe2fd89
7
+ data.tar.gz: 652d9e6c68f1688202d7ee52617bb73491f358afffcd99960bda6edc3acf2a6dd2942e278bcfeb7e86876754f16d9b8911af716b61eb179f021188ecd61e9dc8
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.2
@@ -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,13 @@ module RESTFramework::BaseModelControllerMixin
33
37
  filterset_fields: nil,
34
38
  ordering_fields: nil,
35
39
  ordering_query_param: 'ordering',
40
+ search_fields: nil,
41
+ search_query_param: 'search',
42
+ search_case_sensitive: false,
36
43
 
37
44
  # Other misc attributes.
38
- disable_creation_from_recordset: false, # Option to disable `recordset.create` behavior.
45
+ create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
46
+ filter_recordset_before_find: true, # Option to control if filtering is done before find.
39
47
  }.each do |a, default|
40
48
  unless base.respond_to?(a)
41
49
  base.class_attribute(a)
@@ -60,11 +68,6 @@ module RESTFramework::BaseModelControllerMixin
60
68
  return (action_config[action] if action) || self.class.send(generic_config_key)
61
69
  end
62
70
 
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
71
  # Get a list of fields for the current action.
69
72
  def get_fields
70
73
  return (
@@ -74,14 +77,37 @@ module RESTFramework::BaseModelControllerMixin
74
77
  )
75
78
  end
76
79
 
80
+ # Get a list of find_by fields for the current action.
81
+ def get_find_by_fields
82
+ return self.class.find_by_fields&.map(&:to_s) || self.get_fields
83
+ end
84
+
85
+ # Get a list of find_by fields for the current action.
86
+ def get_filterset_fields
87
+ return self.class.filterset_fields&.map(&:to_s) || self.get_fields
88
+ end
89
+
90
+ # Get a list of ordering fields for the current action.
91
+ def get_ordering_fields
92
+ return self.class.ordering_fields&.map(&:to_s) || self.get_fields
93
+ end
94
+
95
+ # Get a list of search fields for the current action.
96
+ def get_search_fields
97
+ return self.class.search_fields&.map(&:to_s) || self.get_fields
98
+ end
99
+
100
+ # Get a list of parameters allowed for the current action.
101
+ def get_allowed_parameters
102
+ return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
103
+ end
104
+
77
105
  # Helper to get the configured serializer class, or `NativeSerializer` as a default.
78
- # @return [RESTFramework::BaseSerializer]
79
106
  def get_serializer_class
80
107
  return self.class.serializer_class || RESTFramework::NativeSerializer
81
108
  end
82
109
 
83
- # Get the list of filtering backends to use.
84
- # @return [RESTFramework::BaseFilter]
110
+ # Helper to get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
85
111
  def get_filter_backends
86
112
  return self.class.filter_backends || [
87
113
  RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
@@ -94,9 +120,7 @@ module RESTFramework::BaseModelControllerMixin
94
120
  fields = self.get_allowed_parameters || self.get_fields
95
121
 
96
122
  # Filter the request body.
97
- body_params = request.request_parameters.select { |p|
98
- fields.include?(p)
99
- }
123
+ body_params = request.request_parameters.select { |p| fields.include?(p) }
100
124
 
101
125
  # Add query params in place of missing body params, if configured.
102
126
  if self.class.accept_generic_params_as_body_params
@@ -113,9 +137,7 @@ module RESTFramework::BaseModelControllerMixin
113
137
  end
114
138
 
115
139
  # Filter fields in exclude_body_fields.
116
- (self.class.exclude_body_fields || []).each do |f|
117
- body_params.delete(f.to_s)
118
- end
140
+ (self.class.exclude_body_fields || []).each { |f| body_params.delete(f.to_s) }
119
141
 
120
142
  body_params
121
143
  end
@@ -123,14 +145,6 @@ module RESTFramework::BaseModelControllerMixin
123
145
  alias :get_create_params :get_body_params
124
146
  alias :get_update_params :get_body_params
125
147
 
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
148
  # Get the model for this controller.
135
149
  def get_model(from_get_recordset: false)
136
150
  return @model if instance_variable_defined?(:@model) && @model
@@ -164,6 +178,29 @@ module RESTFramework::BaseModelControllerMixin
164
178
 
165
179
  return nil
166
180
  end
181
+
182
+ # Get a single record by primary key or another column, if allowed.
183
+ def get_record
184
+ recordset = self.get_recordset
185
+ find_by_fields = self.get_find_by_fields
186
+ find_by_key = self.get_model.primary_key
187
+
188
+ # Find by another column if it's permitted.
189
+ if find_by_query_param && params[find_by_query_param].in?(find_by_fields)
190
+ find_by_key = params[find_by_query_param]
191
+ end
192
+
193
+ # Filter recordset, if configured.
194
+ if self.filter_recordset_before_find
195
+ recordset = self.get_filtered_data(recordset)
196
+ end
197
+
198
+ # Return the record.
199
+ if find_by_value = params[:id] # Route key is always :id by Rails convention.
200
+ return self.get_recordset.find_by!(find_by_key => find_by_value)
201
+ end
202
+ return nil
203
+ end
167
204
  end
168
205
 
169
206
 
@@ -200,8 +237,8 @@ end
200
237
  # Mixin for creating records.
201
238
  module RESTFramework::CreateModelMixin
202
239
  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).
240
+ if self.get_recordset.respond_to?(:create!) && self.create_from_recordset
241
+ # Create with any properties inherited from the recordset.
205
242
  @record = self.get_recordset.create!(self.get_create_params)
206
243
  else
207
244
  # 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
39
+ return nil if @controller.class.ordering_query_param.blank?
40
+ ordering_fields = @controller.send(:get_ordering_fields)
40
41
 
41
42
  order_string = @controller.params[@controller.class.ordering_query_param]
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
@@ -63,6 +71,20 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
63
71
  end
64
72
 
65
73
 
66
- # TODO: implement searching within fields rather than exact match filtering (ModelFilter)
67
- # class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
68
- # end
74
+ # Multi-field text searching on models.
75
+ class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
76
+ # Filter data according to the request query parameters.
77
+ def get_filtered_data(data)
78
+ fields = @controller.send(:get_search_fields)
79
+ search = @controller.request.query_parameters[@controller.send(:search_query_param)]
80
+
81
+ # Ensure we use array conditions to prevent SQL injection.
82
+ unless search.blank?
83
+ return data.where(fields.map { |f|
84
+ "CAST(#{f} AS text) #{@controller.send(:search_case_sensitive) ? "LIKE" : "ILIKE"} ?"
85
+ }.join(' OR '), *(["%#{search}%"] * fields.length))
86
+ end
87
+
88
+ return data
89
+ end
90
+ 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
@@ -31,7 +31,9 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
31
31
  # Determine model either explicitly, or by inspecting @object or @controller.
32
32
  @model = model
33
33
  @model ||= @object.class if @object.is_a?(ActiveRecord::Base)
34
- @model ||= @object[0].class if @many && @object[0].is_a?(ActiveRecord::Base)
34
+ @model ||= @object[0].class if (
35
+ @many && @object.is_a?(Enumerable) && @object.is_a?(ActiveRecord::Base)
36
+ )
35
37
  @model ||= @controller.send(:get_model) if @controller
36
38
  end
37
39
 
@@ -119,29 +121,21 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
119
121
 
120
122
  # Allow a serializer instance to be used as a hash directly in a nested serializer config.
121
123
  def [](key)
122
- unless instance_variable_defined?(:@_nested_config)
123
- @_nested_config = self.get_serializer_config
124
- end
124
+ @_nested_config ||= self.get_serializer_config
125
125
  return @_nested_config[key]
126
126
  end
127
127
  def []=(key, value)
128
- unless instance_variable_defined?(:@_nested_config)
129
- @_nested_config = self.get_serializer_config
130
- end
128
+ @_nested_config ||= self.get_serializer_config
131
129
  return @_nested_config[key] = value
132
130
  end
133
131
 
134
132
  # Allow a serializer class to be used as a hash directly in a nested serializer config.
135
133
  def self.[](key)
136
- unless instance_variable_defined?(:@_nested_config)
137
- @_nested_config = self.new.get_serializer_config
138
- end
134
+ @_nested_config ||= self.new.get_serializer_config
139
135
  return @_nested_config[key]
140
136
  end
141
137
  def self.[]=(key, value)
142
- unless instance_variable_defined?(:@_nested_config)
143
- @_nested_config = self.new.get_serializer_config
144
- end
138
+ @_nested_config ||= self.new.get_serializer_config
145
139
  return @_nested_config[key] = value
146
140
  end
147
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.1
4
+ version: 0.3.2
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.1