rest_framework 0.2.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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