rest_framework 0.2.0 → 0.3.0

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: 8ef7499f4d1e10af1f2520d3b9847dc7f711bd1c7f4377efe21bd5b60c0dd9a7
4
- data.tar.gz: b15a9b0abee58e315fdc3a1157ca04d7051630821d3ed2ca691564966b5fefca
3
+ metadata.gz: 5a4c53fa557df9090c749745efce556214252b76e2115df09917970e9a9c8b5e
4
+ data.tar.gz: 5c33946ae697618f726532e9b45056f2e112d3f81f6efb5c9a37ab8d9b3b002e
5
5
  SHA512:
6
- metadata.gz: fb586a083989a67f206d5b5d6c14eb546949569e384d3b44c870de5f49f610b5848d53da87e01234542705304dd7d49bcf6f0037f0b89331c7fa2a1b084422ce
7
- data.tar.gz: fe4c960cda0d2a9ba43cb7d5b1cfa84963802800829323c26b350841bbd34290e68cbbc4f14a3138a1cb95a28aafff76b2d9a78a41772950e00ad80159c8f2f3
6
+ metadata.gz: ddfd6544b96284e871684d2f448e622631bd88631ea78ebe4170d2ff2fe4f1654901ef86a96584e3c5d0a2538bcef5d29f85152795aa689df58fc98cb05f7b3b
7
+ data.tar.gz: 1ab60515fcf543c0df574a6cf3913648736d19d559b1d9fbe55f891ce3e6926026357369580430ada33b6e294a081d8058b297bb3f146b3d17f4671b10683d55
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.0
@@ -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
@@ -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,
@@ -35,7 +39,8 @@ module RESTFramework::BaseModelControllerMixin
35
39
  ordering_query_param: 'ordering',
36
40
 
37
41
  # Other misc attributes.
38
- disable_creation_from_recordset: false, # Option to disable `recordset.create` behavior.
42
+ create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
43
+ filter_recordset_before_find: true, # Option to control if filtering is done before find.
39
44
  }.each do |a, default|
40
45
  unless base.respond_to?(a)
41
46
  base.class_attribute(a)
@@ -60,11 +65,6 @@ module RESTFramework::BaseModelControllerMixin
60
65
  return (action_config[action] if action) || self.class.send(generic_config_key)
61
66
  end
62
67
 
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
68
  # Get a list of fields for the current action.
69
69
  def get_fields
70
70
  return (
@@ -74,14 +74,32 @@ module RESTFramework::BaseModelControllerMixin
74
74
  )
75
75
  end
76
76
 
77
+ # Get a list of find_by fields for the current action.
78
+ def get_find_by_fields
79
+ return self.class.find_by_fields&.map(&:to_s) || self.get_fields
80
+ end
81
+
82
+ # Get a list of find_by fields for the current action.
83
+ def get_filterset_fields
84
+ return self.class.filterset_fields&.map(&:to_s) || self.get_fields
85
+ end
86
+
87
+ # Get a list of ordering fields for the current action.
88
+ def get_ordering_fields
89
+ return self.class.ordering_fields&.map(&:to_s) || self.get_fields
90
+ end
91
+
92
+ # Get a list of parameters allowed for the current action.
93
+ def get_allowed_parameters
94
+ return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
95
+ end
96
+
77
97
  # Helper to get the configured serializer class, or `NativeSerializer` as a default.
78
- # @return [RESTFramework::BaseSerializer]
79
98
  def get_serializer_class
80
99
  return self.class.serializer_class || RESTFramework::NativeSerializer
81
100
  end
82
101
 
83
- # Get the list of filtering backends to use.
84
- # @return [RESTFramework::BaseFilter]
102
+ # Helper to get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
85
103
  def get_filter_backends
86
104
  return self.class.filter_backends || [
87
105
  RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
@@ -94,9 +112,7 @@ module RESTFramework::BaseModelControllerMixin
94
112
  fields = self.get_allowed_parameters || self.get_fields
95
113
 
96
114
  # Filter the request body.
97
- body_params = request.request_parameters.select { |p|
98
- fields.include?(p)
99
- }
115
+ body_params = request.request_parameters.select { |p| fields.include?(p) }
100
116
 
101
117
  # Add query params in place of missing body params, if configured.
102
118
  if self.class.accept_generic_params_as_body_params
@@ -113,9 +129,7 @@ module RESTFramework::BaseModelControllerMixin
113
129
  end
114
130
 
115
131
  # Filter fields in exclude_body_fields.
116
- (self.class.exclude_body_fields || []).each do |f|
117
- body_params.delete(f.to_s)
118
- end
132
+ (self.class.exclude_body_fields || []).each { |f| body_params.delete(f.to_s) }
119
133
 
120
134
  body_params
121
135
  end
@@ -123,14 +137,6 @@ module RESTFramework::BaseModelControllerMixin
123
137
  alias :get_create_params :get_body_params
124
138
  alias :get_update_params :get_body_params
125
139
 
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
140
  # Get the model for this controller.
135
141
  def get_model(from_get_recordset: false)
136
142
  return @model if instance_variable_defined?(:@model) && @model
@@ -164,6 +170,29 @@ module RESTFramework::BaseModelControllerMixin
164
170
 
165
171
  return nil
166
172
  end
173
+
174
+ # Get a single record by primary key or another column, if allowed.
175
+ def get_record
176
+ recordset = self.get_recordset
177
+ find_by_fields = self.get_find_by_fields
178
+ find_by_key = self.get_model.primary_key
179
+
180
+ # Find by another column if it's permitted.
181
+ if find_by_query_param && params[find_by_query_param].in?(find_by_fields)
182
+ find_by_key = params[find_by_query_param]
183
+ end
184
+
185
+ # Filter recordset, if configured.
186
+ if self.filter_recordset_before_find
187
+ recordset = self.get_filtered_data(recordset)
188
+ end
189
+
190
+ # Return the record.
191
+ if find_by_value = params[:id] # Route key is always :id by Rails convention.
192
+ return self.get_recordset.find_by!(find_by_key => find_by_value)
193
+ end
194
+ return nil
195
+ end
167
196
  end
168
197
 
169
198
 
@@ -200,8 +229,8 @@ end
200
229
  # Mixin for creating records.
201
230
  module RESTFramework::CreateModelMixin
202
231
  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).
232
+ if self.get_recordset.respond_to?(:create!) && self.create_from_recordset
233
+ # Create with any properties inherited from the recordset.
205
234
  @record = self.get_recordset.create!(self.get_create_params)
206
235
  else
207
236
  # 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,7 +14,7 @@ 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)
17
+ fields = @controller.send(:get_filterset_fields)
18
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
@@ -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
@@ -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
@@ -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.
@@ -72,6 +72,16 @@ module ActionDispatch::Routing
72
72
  return controller
73
73
  end
74
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
+
75
85
  # Internal core implementation of the `rest_resource(s)` router, both singular and plural.
76
86
  # @param default_singular [Boolean] the default plurality of the resource if the plurality is
77
87
  # not otherwise defined by the controller
@@ -89,9 +99,11 @@ module ActionDispatch::Routing
89
99
  kwargs[:controller] = name unless kwargs[:controller]
90
100
 
91
101
  # determine plural/singular resource
92
- if kwargs.delete(:force_singular)
102
+ force_singular = kwargs.delete(:force_singular)
103
+ force_plural = kwargs.delete(:force_plural)
104
+ if force_singular
93
105
  singular = true
94
- elsif kwargs.delete(:force_plural)
106
+ elsif force_plural
95
107
  singular = false
96
108
  elsif !controller_class.singleton_controller.nil?
97
109
  singular = controller_class.singleton_controller
@@ -100,28 +112,20 @@ module ActionDispatch::Routing
100
112
  end
101
113
  resource_method = singular ? :resource : :resources
102
114
 
103
- # call either `resource` or `resources`, passing appropriate modifiers
115
+ # Call either `resource` or `resources`, passing appropriate modifiers.
104
116
  skip_undefined = kwargs.delete(:skip_undefined) || true
105
117
  skip = controller_class.get_skip_actions(skip_undefined: skip_undefined)
106
118
  public_send(resource_method, name, except: skip, **kwargs) do
107
119
  if controller_class.respond_to?(:extra_member_actions)
108
120
  member do
109
121
  actions = self._parse_extra_actions(controller_class.extra_member_actions)
110
- actions.each do |action_config|
111
- action_config[:methods].each do |m|
112
- public_send(m, action_config[:path], **action_config[:kwargs])
113
- end
114
- end
122
+ _route_extra_actions(actions)
115
123
  end
116
124
  end
117
125
 
118
126
  collection do
119
127
  actions = self._parse_extra_actions(controller_class.extra_actions)
120
- actions.each do |action_config|
121
- action_config[:methods].each do |m|
122
- public_send(m, action_config[:path], **action_config[:kwargs])
123
- end
124
- end
128
+ _route_extra_actions(actions)
125
129
  end
126
130
 
127
131
  yield if block_given?
@@ -145,6 +149,7 @@ module ActionDispatch::Routing
145
149
  # Route a controller without the default resourceful paths.
146
150
  def rest_route(name=nil, **kwargs, &block)
147
151
  controller = kwargs.delete(:controller) || name
152
+ route_root_to = kwargs.delete(:route_root_to)
148
153
  if controller.is_a?(Class)
149
154
  controller_class = controller
150
155
  else
@@ -157,42 +162,27 @@ module ActionDispatch::Routing
157
162
  # Route actions using the resourceful router, but skip all builtin actions.
158
163
  actions = self._parse_extra_actions(controller_class.extra_actions)
159
164
  public_send(:resource, name, only: [], **kwargs) do
160
- actions.each do |action_config|
161
- action_config[:methods].each do |m|
162
- public_send(m, action_config[:path], **action_config[:kwargs])
163
- end
164
- yield if block_given?
165
+ # Route a root for this resource.
166
+ if route_root_to
167
+ get '', action: route_root_to
165
168
  end
169
+
170
+ _route_extra_actions(actions, &block)
166
171
  end
167
172
  end
168
173
 
169
174
  # Route a controller's `#root` to '/' in the current scope/namespace, along with other actions.
170
- # @param name [Symbol] the snake_case name of the controller
171
175
  def rest_root(name=nil, **kwargs, &block)
172
176
  # By default, use RootController#root.
173
177
  root_action = kwargs.delete(:action) || :root
174
178
  controller = kwargs.delete(:controller) || name || :root
175
179
 
176
- # Route the root.
177
- get name.to_s, controller: controller, action: root_action
178
-
179
- # Route any additional actions.
180
- controller_class = self._get_controller_class(controller, pluralize: false)
181
- actions = self._parse_extra_actions(controller_class.extra_actions)
182
- actions.each do |action_config|
183
- # Add :action unless kwargs defines it.
184
- unless action_config[:kwargs].key?(:action)
185
- action_config[:kwargs][:action] = action_config[:path]
186
- end
180
+ # Remove path if name is nil (routing to the root of current namespace).
181
+ unless name
182
+ kwargs[:path] = ''
183
+ end
187
184
 
188
- action_config[:methods].each do |m|
189
- public_send(
190
- m,
191
- File.join(name.to_s, action_config[:path].to_s),
192
- controller: controller,
193
- **action_config[:kwargs],
194
- )
195
- end
185
+ return rest_route(controller, route_root_to: root_action, **kwargs) do
196
186
  yield if block_given?
197
187
  end
198
188
  end
@@ -3,6 +3,10 @@ class RESTFramework::BaseSerializer
3
3
  @object = object
4
4
  @controller = controller
5
5
  end
6
+
7
+ def serialize
8
+ raise NotImplementedError
9
+ end
6
10
  end
7
11
 
8
12
 
@@ -27,7 +31,9 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
27
31
  # Determine model either explicitly, or by inspecting @object or @controller.
28
32
  @model = model
29
33
  @model ||= @object.class if @object.is_a?(ActiveRecord::Base)
30
- @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
+ )
31
37
  @model ||= @controller.send(:get_model) if @controller
32
38
  end
33
39
 
@@ -115,29 +121,21 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
115
121
 
116
122
  # Allow a serializer instance to be used as a hash directly in a nested serializer config.
117
123
  def [](key)
118
- unless instance_variable_defined?(:@_nested_config)
119
- @_nested_config = self.get_serializer_config
120
- end
124
+ @_nested_config ||= self.get_serializer_config
121
125
  return @_nested_config[key]
122
126
  end
123
127
  def []=(key, value)
124
- unless instance_variable_defined?(:@_nested_config)
125
- @_nested_config = self.get_serializer_config
126
- end
128
+ @_nested_config ||= self.get_serializer_config
127
129
  return @_nested_config[key] = value
128
130
  end
129
131
 
130
132
  # Allow a serializer class to be used as a hash directly in a nested serializer config.
131
133
  def self.[](key)
132
- unless instance_variable_defined?(:@_nested_config)
133
- @_nested_config = self.new.get_serializer_config
134
- end
134
+ @_nested_config ||= self.new.get_serializer_config
135
135
  return @_nested_config[key]
136
136
  end
137
137
  def self.[]=(key, value)
138
- unless instance_variable_defined?(:@_nested_config)
139
- @_nested_config = self.new.get_serializer_config
140
- end
138
+ @_nested_config ||= self.new.get_serializer_config
141
139
  return @_nested_config[key] = value
142
140
  end
143
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.0
4
+ version: 0.3.0
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-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -33,12 +33,12 @@ 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
40
  - app/views/rest_framework/default.html.erb
40
41
  - lib/rest_framework.rb
41
- - lib/rest_framework/VERSION_STAMP
42
42
  - lib/rest_framework/controller_mixins.rb
43
43
  - lib/rest_framework/controller_mixins/base.rb
44
44
  - lib/rest_framework/controller_mixins/models.rb
@@ -1 +0,0 @@
1
- 0.2.0