rest_framework 0.0.13 → 0.1.1

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: 1c74e7a6e690c22a72a04bb4fd30132b845ef108890c8140673300ca2c047f2a
4
- data.tar.gz: 317f484c3bf46554b5664bc30d20c611e404c4893725c77a84372ebac8e7ca82
3
+ metadata.gz: 7cb7ba6d5e7861525dca18f908afa1f398f614af654223ac03de15f7014dafaa
4
+ data.tar.gz: 7938b5b0225e089f2cd3a168b80763ebcc63dd65655ae4c87b045e923477191a
5
5
  SHA512:
6
- metadata.gz: 2e4dbb91f448068ab3115184518f75a633708e562268863b12f81838a2bcd3a5c344af9390e2238210a52e1070b1d2a2dd51f4b23bc2e51fdd1773e6c702b137
7
- data.tar.gz: 21a18513bfc9682fd57e01d46f63b9172864bbbbd140c8d060c22e28df5896352c547b05fceda4914ac89da7d8c76e56469ace66f7987e30e85c6d24d66b2b35
6
+ metadata.gz: cb1583fe183387972e1537bc2b90ebe9b224381e46515ffd202f3ad86cb4a545f906264644fff537b5937f8e4f985f39eb3123c6d47e2e8e5c1be31b425aad54
7
+ data.tar.gz: 3a8b3a6e63a4ca03250d8e6697eda885abd6940f8b580f073ffa0c237a59a535d4997aa4591651c01cda61fd3f021508ed69d298a19512f783a9604b6dbcdd7c
data/README.md CHANGED
@@ -3,15 +3,20 @@
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
 
6
- Rails REST Framework helps you build awesome Web APIs in Ruby on Rails.
6
+ A framework for DRY RESTful APIs in Ruby on Rails.
7
7
 
8
8
  **The Problem**: Building controllers for APIs usually involves writing a lot of redundant CRUD
9
- logic, and routing them can be obnoxious.
9
+ logic, and routing them can be obnoxious. Building and maintaining features like ordering,
10
+ filtering, and pagination can be tedious.
10
11
 
11
- **The Solution**: This gem handles the common logic so you can focus on the parts of your API which
12
- make it unique.
12
+ **The Solution**: This framework implements browsable API responses, CRUD actions for your models,
13
+ and features like ordering/filtering/pagination, so you can focus on building awesome APIs.
13
14
 
14
- To see detailed documentation, visit https://rails-rest-framework.com.
15
+ Website/Guide: [https://rails-rest-framework.com](https://rails-rest-framework.com)
16
+
17
+ Source: [https://github.com/gregschmit/rails-rest-framework](https://github.com/gregschmit/rails-rest-framework)
18
+
19
+ YARD Docs: [https://rubydoc.info/gems/rest_framework](https://rubydoc.info/gems/rest_framework)
15
20
 
16
21
  ## Installation
17
22
 
@@ -110,24 +115,12 @@ end
110
115
  After you clone the repository, cd'ing into the directory should create a new gemset if you are
111
116
  using RVM. Then run `bundle install` to install the appropriate gems.
112
117
 
113
- To run the full test suite:
118
+ To run the test suite:
114
119
 
115
120
  ```shell
116
121
  $ rake test
117
122
  ```
118
123
 
119
- To run unit tests:
120
-
121
- ```shell
122
- $ rake test:unit
123
- ```
124
-
125
- To run integration tests:
126
-
127
- ```shell
128
- $ rake test:integration
129
- ```
130
-
131
- To interact with the integration app, you can `cd test/integration` and operate it via the normal
132
- Rails interfaces. Ensure you run `rake db:schema:load` before running `rails server` or
133
- `rails console`.
124
+ To interact with the test app, `cd test` and operate it via the normal Rails interfaces. Ensure you
125
+ run `rake db:schema:load` before running `rails server` or `rails console`. You can also load the
126
+ test fixtures with `rake db:fixtures:load`.
@@ -30,14 +30,14 @@
30
30
  <% if @json_payload %>
31
31
  <li class="nav-item">
32
32
  <a class="nav-link active" href="#tab-json" data-toggle="tab" role="tab">
33
- JSON
33
+ .json
34
34
  </a>
35
35
  </li>
36
36
  <% end %>
37
37
  <% if @xml_payload %>
38
38
  <li class="nav-item">
39
39
  <a class="nav-link" href="#tab-xml" data-toggle="tab" role="tab">
40
- XML
40
+ .xml
41
41
  </a>
42
42
  </li>
43
43
  <% end %>
@@ -1,7 +1,11 @@
1
1
  module RESTFramework
2
2
  end
3
3
 
4
+
4
5
  require_relative "rest_framework/controller_mixins"
5
6
  require_relative "rest_framework/engine"
7
+ require_relative "rest_framework/filters"
8
+ require_relative "rest_framework/paginators"
6
9
  require_relative "rest_framework/routers"
10
+ require_relative "rest_framework/serializers"
7
11
  require_relative "rest_framework/version"
@@ -1 +1 @@
1
- 0.0.13
1
+ 0.1.1
@@ -1,2 +1,6 @@
1
+ module RESTFramework::ControllerMixins
2
+ end
3
+
4
+
1
5
  require_relative 'controller_mixins/base'
2
6
  require_relative 'controller_mixins/models'
@@ -1,129 +1,180 @@
1
- module RESTFramework
2
-
3
- # This module provides the common functionality for any controller mixins, a `root` action, and
4
- # the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
5
- # is defined.
6
- module BaseControllerMixin
7
- # Default action for API root.
8
- def root
9
- api_response({message: "This is the root of your awesome API!"})
10
- end
1
+ require_relative '../serializers'
11
2
 
12
- module ClassMethods
13
- def get_skip_actions(skip_undefined: true)
14
- # first, skip explicitly skipped actions
15
- skip = self.skip_actions || []
16
3
 
17
- # now add methods which don't exist, since we don't want to route those
18
- if skip_undefined
19
- [:index, :new, :create, :show, :edit, :update, :destroy].each do |a|
20
- skip << a unless self.method_defined?(a)
21
- end
22
- end
4
+ # This module provides the common functionality for any controller mixins, a `root` action, and
5
+ # the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
6
+ # is defined.
7
+ module RESTFramework::BaseControllerMixin
8
+ # Default action for API root.
9
+ def root
10
+ api_response({message: "This is the root of your awesome API!"})
11
+ end
23
12
 
24
- return skip
13
+ module ClassMethods
14
+ # Helper to get the actions that should be skipped.
15
+ def get_skip_actions(skip_undefined: true)
16
+ # First, skip explicitly skipped actions.
17
+ skip = self.skip_actions || []
18
+
19
+ # Now add methods which don't exist, since we don't want to route those.
20
+ if skip_undefined
21
+ [:index, :new, :create, :show, :edit, :update, :destroy].each do |a|
22
+ skip << a unless self.method_defined?(a)
23
+ end
25
24
  end
25
+
26
+ return skip
26
27
  end
28
+ end
29
+
30
+ def self.included(base)
31
+ if base.is_a? Class
32
+ base.extend ClassMethods
27
33
 
28
- def self.included(base)
29
- if base.is_a? Class
30
- base.extend ClassMethods
31
- base.class_attribute(*[
32
- :singleton_controller,
33
- :extra_actions,
34
- :skip_actions,
35
- :paginator_class,
36
- ])
37
- base.rescue_from(ActiveRecord::RecordNotFound, with: :record_not_found)
38
- base.rescue_from(ActiveRecord::RecordInvalid, with: :record_invalid)
39
- base.rescue_from(ActiveRecord::RecordNotSaved, with: :record_not_saved)
40
- base.rescue_from(ActiveRecord::RecordNotDestroyed, with: :record_not_destroyed)
34
+ # Add class attributes (with defaults) unless they already exist.
35
+ {
36
+ extra_actions: nil,
37
+ extra_member_actions: nil,
38
+ filter_backends: nil,
39
+ paginator_class: nil,
40
+ page_size: 20,
41
+ page_query_param: 'page',
42
+ page_size_query_param: 'page_size',
43
+ max_page_size: nil,
44
+ serializer_class: nil,
45
+ singleton_controller: nil,
46
+ skip_actions: nil,
47
+ }.each do |a, default|
48
+ unless base.respond_to?(a)
49
+ base.class_attribute(a)
50
+
51
+ # Set default manually so we can still support Rails 4. Maybe later we can use the
52
+ # default parameter on `class_attribute`.
53
+ base.send(:"#{a}=", default)
54
+ end
41
55
  end
42
- end
43
56
 
44
- protected
57
+ # Alias `extra_actions` to `extra_collection_actions`.
58
+ unless base.respond_to?(:extra_collection_actions)
59
+ base.alias_method(:extra_collection_actions, :extra_actions)
60
+ base.alias_method(:extra_collection_actions=, :extra_actions=)
61
+ end
45
62
 
46
- def record_invalid(e)
47
- return api_response(
48
- {message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
49
- )
50
- end
63
+ # skip csrf since this is an API
64
+ base.skip_before_action(:verify_authenticity_token) rescue nil
51
65
 
52
- def record_not_found(e)
53
- return api_response({message: "Record not found.", exception: e}, status: 404)
66
+ # handle some common exceptions
67
+ base.rescue_from(ActiveRecord::RecordNotFound, with: :record_not_found)
68
+ base.rescue_from(ActiveRecord::RecordInvalid, with: :record_invalid)
69
+ base.rescue_from(ActiveRecord::RecordNotSaved, with: :record_not_saved)
70
+ base.rescue_from(ActiveRecord::RecordNotDestroyed, with: :record_not_destroyed)
54
71
  end
72
+ end
55
73
 
56
- def record_not_saved(e)
57
- return api_response({message: "Record not saved.", exception: e}, status: 406)
58
- end
74
+ protected
59
75
 
60
- def record_not_destroyed(e)
61
- return api_response({message: "Record not destroyed.", exception: e}, status: 406)
76
+ # Helper to get filtering backends with a sane default.
77
+ # @return [RESTFramework::BaseFilter]
78
+ def get_filter_backends
79
+ return self.class.filter_backends || []
80
+ end
81
+
82
+ # Helper to filter an arbitrary data set over all configured filter backends.
83
+ def get_filtered_data(data)
84
+ self.get_filter_backends.each do |filter_class|
85
+ filter = filter_class.new(controller: self)
86
+ data = filter.get_filtered_data(data)
62
87
  end
63
88
 
64
- def _get_routes
65
- begin
66
- formatter = ActionDispatch::Routing::ConsoleFormatter::Sheet
67
- rescue NameError
68
- formatter = ActionDispatch::Routing::ConsoleFormatter
69
- end
70
- return ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes).format(
71
- formatter.new
72
- ).lines.drop(1).map { |r| r.split.last(3) }.map { |r|
73
- {verb: r[0], path: r[1], action: r[2]}
74
- }.select { |r| r[:path].start_with?(request.path) }
89
+ return data
90
+ end
91
+
92
+ # Helper to get the configured serializer class.
93
+ # @return [RESTFramework::BaseSerializer]
94
+ def get_serializer_class
95
+ return self.class.serializer_class
96
+ end
97
+
98
+ def record_invalid(e)
99
+ return api_response(
100
+ {message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
101
+ )
102
+ end
103
+
104
+ def record_not_found(e)
105
+ return api_response({message: "Record not found.", exception: e}, status: 404)
106
+ end
107
+
108
+ def record_not_saved(e)
109
+ return api_response({message: "Record not saved.", exception: e}, status: 406)
110
+ end
111
+
112
+ def record_not_destroyed(e)
113
+ return api_response({message: "Record not destroyed.", exception: e}, status: 406)
114
+ end
115
+
116
+ # Helper for showing routes under a controller action, used for the browsable API.
117
+ def _get_routes
118
+ begin
119
+ formatter = ActionDispatch::Routing::ConsoleFormatter::Sheet
120
+ rescue NameError
121
+ formatter = ActionDispatch::Routing::ConsoleFormatter
75
122
  end
123
+ return ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes).format(
124
+ formatter.new
125
+ ).lines.drop(1).map { |r| r.split.last(3) }.map { |r|
126
+ {verb: r[0], path: r[1], action: r[2]}
127
+ }.select { |r| r[:path].start_with?(request.path) }
128
+ end
129
+
130
+ # Helper to render a browsable API for `html` format, along with basic `json`/`xml` formats, and
131
+ # with support or passing custom `kwargs` to the underlying `render` calls.
132
+ def api_response(payload, html_kwargs: nil, json_kwargs: nil, xml_kwargs: nil, **kwargs)
133
+ html_kwargs ||= {}
134
+ json_kwargs ||= {}
135
+ xml_kwargs ||= {}
136
+
137
+ # allow blank (no-content) responses
138
+ @blank = kwargs[:blank]
76
139
 
77
- # Helper alias for `respond_to`/`render`. `payload` should be already serialized to Ruby
78
- # primitives.
79
- def api_response(payload, html_kwargs: nil, json_kwargs: nil, xml_kwargs: nil, **kwargs)
80
- html_kwargs ||= {}
81
- json_kwargs ||= {}
82
- xml_kwargs ||= {}
83
-
84
- # allow blank (no-content) responses
85
- @blank = kwargs[:blank]
86
-
87
- respond_to do |format|
88
- if @blank
89
- format.json {head :no_content}
90
- format.xml {head :no_content}
91
- else
92
- if payload.respond_to?(:to_json)
93
- format.json {
94
- kwargs = kwargs.merge(json_kwargs)
95
- render(json: payload, layout: false, **kwargs)
96
- }
97
- end
98
- if payload.respond_to?(:to_xml)
99
- format.xml {
100
- kwargs = kwargs.merge(xml_kwargs)
101
- render(xml: payload, layout: false, **kwargs)
102
- }
103
- end
140
+ respond_to do |format|
141
+ if @blank
142
+ format.json {head :no_content}
143
+ format.xml {head :no_content}
144
+ else
145
+ if payload.respond_to?(:to_json)
146
+ format.json {
147
+ jkwargs = kwargs.merge(json_kwargs)
148
+ render(json: payload, layout: false, **jkwargs)
149
+ }
150
+ end
151
+ if payload.respond_to?(:to_xml)
152
+ format.xml {
153
+ xkwargs = kwargs.merge(xml_kwargs)
154
+ render(xml: payload, layout: false, **xkwargs)
155
+ }
104
156
  end
105
- format.html {
106
- @payload = payload
107
- @json_payload = ''
108
- @xml_payload = ''
109
- unless @blank
110
- @json_payload = payload.to_json if payload.respond_to?(:to_json)
111
- @xml_payload = payload.to_xml if payload.respond_to?(:to_xml)
112
- end
113
- @template_logo_text ||= "Rails REST Framework"
114
- @title ||= self.controller_name.camelize
115
- @routes ||= self._get_routes
116
- kwargs = kwargs.merge(html_kwargs)
117
- begin
118
- render(**kwargs)
119
- rescue ActionView::MissingTemplate # fallback to rest_framework layout/view
120
- kwargs[:layout] = "rest_framework"
121
- kwargs[:template] = "rest_framework/default"
122
- end
123
- render(**kwargs)
124
- }
125
157
  end
158
+ format.html {
159
+ @payload = payload
160
+ @json_payload = ''
161
+ @xml_payload = ''
162
+ unless @blank
163
+ @json_payload = payload.to_json if payload.respond_to?(:to_json)
164
+ @xml_payload = payload.to_xml if payload.respond_to?(:to_xml)
165
+ end
166
+ @template_logo_text ||= "Rails REST Framework"
167
+ @title ||= self.controller_name.camelize
168
+ @routes ||= self._get_routes
169
+ hkwargs = kwargs.merge(html_kwargs)
170
+ begin
171
+ render(**hkwargs)
172
+ rescue ActionView::MissingTemplate # fallback to rest_framework layout/view
173
+ hkwargs[:layout] = "rest_framework"
174
+ hkwargs[:template] = "rest_framework/default"
175
+ render(**hkwargs)
176
+ end
177
+ }
126
178
  end
127
179
  end
128
-
129
180
  end
@@ -1,222 +1,257 @@
1
1
  require_relative 'base'
2
- require_relative '../serializers'
3
-
4
- module RESTFramework
5
-
6
- module BaseModelControllerMixin
7
- include BaseControllerMixin
8
- def self.included(base)
9
- if base.is_a? Class
10
- BaseControllerMixin.included(base)
11
- base.class_attribute(*[
12
- :model,
13
- :recordset,
14
- :fields,
15
- :action_fields,
16
- :native_serializer_config,
17
- :native_serializer_action_config,
18
- :filterset_fields,
19
- :allowed_parameters,
20
- :allowed_action_parameters,
21
- :serializer_class,
22
- :extra_member_actions,
23
- ])
24
- base.alias_method(:extra_collection_actions=, :extra_actions=)
2
+ require_relative '../filters'
3
+
4
+
5
+ # This module provides the core functionality for controllers based on models.
6
+ module RESTFramework::BaseModelControllerMixin
7
+ include RESTFramework::BaseControllerMixin
8
+
9
+ def self.included(base)
10
+ if base.is_a? Class
11
+ RESTFramework::BaseControllerMixin.included(base)
12
+
13
+ # Add class attributes (with defaults) unless they already exist.
14
+ {
15
+ # Core attributes related to models.
16
+ model: nil,
17
+ recordset: nil,
18
+
19
+ # Attributes for create/update parameters.
20
+ allowed_parameters: nil,
21
+ allowed_action_parameters: nil,
22
+
23
+ # Attributes for configuring record fields.
24
+ fields: nil,
25
+ action_fields: nil,
26
+
27
+ # Attributes for the default native serializer.
28
+ native_serializer_config: nil,
29
+ native_serializer_action_config: nil,
30
+
31
+ # Attributes for default model filtering (and ordering).
32
+ filterset_fields: nil,
33
+ ordering_fields: nil,
34
+ ordering_query_param: 'ordering',
35
+
36
+ # Other misc attributes.
37
+ disable_creation_from_recordset: nil, # Option to disable `recordset.create` behavior.
38
+ }.each do |a, default|
39
+ unless base.respond_to?(a)
40
+ base.class_attribute(a)
41
+
42
+ # Set default manually so we can still support Rails 4. Maybe later we can use the
43
+ # default parameter on `class_attribute`.
44
+ base.send(:"#{a}=", default)
45
+ end
25
46
  end
26
47
  end
48
+ end
49
+
50
+ protected
27
51
 
28
- protected
52
+ # Get a native serializer config for the current action.
53
+ # @return [RESTFramework::NativeModelSerializer]
54
+ def get_native_serializer_config
55
+ action_serializer_config = self.class.native_serializer_action_config || {}
56
+ action = self.action_name.to_sym
29
57
 
30
- def get_serializer_class
31
- return self.class.serializer_class || NativeModelSerializer
58
+ # Handle case where :index action is not defined.
59
+ if action == :index && !action_serializer_config.key?(:index)
60
+ # Default is :show if `singleton_controller`, otherwise :list.
61
+ action = self.class.singleton_controller ? :show : :list
32
62
  end
33
63
 
34
- # Get a list of fields for the current action.
35
- def get_fields
36
- action_fields = self.class.action_fields || {}
37
- action = self.action_name.to_sym
64
+ return (action_serializer_config[action] if action) || self.class.native_serializer_config
65
+ end
38
66
 
39
- # index action should use :list fields if :index is not provided
40
- action = :list if action == :index && !action_fields.key?(:index)
67
+ # Helper to get the configured serializer class, or `NativeModelSerializer` as a default.
68
+ # @return [RESTFramework::BaseSerializer]
69
+ def get_serializer_class
70
+ return self.class.serializer_class || RESTFramework::NativeModelSerializer
71
+ end
41
72
 
42
- return (action_fields[action] if action) || self.class.fields || []
43
- end
73
+ # Get a list of parameters allowed for the current action.
74
+ def get_allowed_parameters
75
+ allowed_action_parameters = self.class.allowed_action_parameters || {}
76
+ action = self.action_name.to_sym
44
77
 
45
- # Get a native serializer config for the current action.
46
- def get_native_serializer_config
47
- action_serializer_config = self.class.native_serializer_action_config || {}
48
- action = self.action_name.to_sym
78
+ # index action should use :list allowed parameters if :index is not provided
79
+ action = :list if action == :index && !allowed_action_parameters.key?(:index)
49
80
 
50
- # index action should use :list serializer config if :index is not provided
51
- action = :list if action == :index && !action_serializer_config.key?(:index)
81
+ return (allowed_action_parameters[action] if action) || self.class.allowed_parameters
82
+ end
52
83
 
53
- return (action_serializer_config[action] if action) || self.class.native_serializer_config
54
- end
84
+ # Get the list of filtering backends to use.
85
+ # @return [RESTFramework::BaseFilter]
86
+ def get_filter_backends
87
+ return self.class.filter_backends || [
88
+ RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
89
+ ]
90
+ end
55
91
 
56
- # Get a list of parameters allowed for the current action.
57
- def get_allowed_parameters
58
- allowed_action_parameters = self.class.allowed_action_parameters || {}
59
- action = self.action_name.to_sym
92
+ # Get a list of fields for the current action.
93
+ def get_fields
94
+ action_fields = self.class.action_fields || {}
95
+ action = self.action_name.to_sym
60
96
 
61
- # index action should use :list allowed parameters if :index is not provided
62
- action = :list if action == :index && !allowed_action_parameters.key?(:index)
97
+ # index action should use :list fields if :index is not provided
98
+ action = :list if action == :index && !action_fields.key?(:index)
63
99
 
64
- return (allowed_action_parameters[action] if action) || self.class.allowed_parameters
65
- end
100
+ return (action_fields[action] if action) || self.class.fields || []
101
+ end
66
102
 
67
- # Filter the request body for keys in current action's allowed_parameters/fields config.
68
- def _get_parameter_values_from_request_body
69
- fields = self.get_allowed_parameters || self.get_fields
70
- return @_get_parameter_values_from_request_body ||= (request.request_parameters.select { |p|
71
- fields.include?(p.to_sym) || fields.include?(p.to_s)
72
- })
73
- end
74
- alias :get_create_params :_get_parameter_values_from_request_body
75
- alias :get_update_params :_get_parameter_values_from_request_body
76
-
77
- # Filter params for keys allowed by the current action's filterset_fields/fields config.
78
- def _get_filterset_values_from_params
79
- fields = self.filterset_fields || self.get_fields
80
- return @_get_filterset_values_from_params ||= request.query_parameters.select { |p|
81
- fields.include?(p.to_sym) || fields.include?(p.to_s)
82
- }
83
- end
84
- alias :get_lookup_params :_get_filterset_values_from_params
85
- alias :get_filter_params :_get_filterset_values_from_params
86
-
87
- # Get the recordset, filtered by the filter params.
88
- def get_filtered_recordset
89
- filter_params = self.get_filter_params
90
- unless filter_params.blank?
91
- return self.get_recordset.where(**self.get_filter_params.to_hash.symbolize_keys)
92
- end
93
- return self.get_recordset
94
- end
103
+ # Filter the request body for keys in current action's allowed_parameters/fields config.
104
+ def get_body_params
105
+ fields = self.get_allowed_parameters || self.get_fields
106
+ return @get_body_params ||= (request.request_parameters.select { |p|
107
+ fields.include?(p.to_sym) || fields.include?(p.to_s)
108
+ })
109
+ end
110
+ alias :get_create_params :get_body_params
111
+ alias :get_update_params :get_body_params
95
112
 
96
- # Get a record by `id` or return a single record if recordset is filtered down to a single
97
- # record.
98
- def get_record
99
- records = self.get_filtered_recordset
100
- if params['id'] # direct lookup
101
- return records.find(params['id'])
102
- elsif records.length == 1
103
- return records[0]
104
- end
105
- return nil
113
+ # Get a record by the primary key from the filtered recordset.
114
+ def get_record
115
+ records = self.get_filtered_data(self.get_recordset)
116
+ if pk = params[self.model.primary_key]
117
+ return records.find(pk)
106
118
  end
119
+ return nil
120
+ end
107
121
 
108
- # Internal interface for get_model, protecting against infinite recursion with get_recordset.
109
- def _get_model(from_internal_get_recordset: false)
110
- return @model if instance_variable_defined?(:@model) && @model
111
- return self.class.model if self.class.model
112
- unless from_internal_get_recordset # prevent infinite recursion
113
- recordset = self._get_recordset(from_internal_get_model: true)
114
- return (@model = recordset.klass) if recordset
115
- end
116
- begin
117
- return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
118
- rescue NameError
119
- end
120
- return nil
122
+ # Internal interface for get_model, protecting against infinite recursion with get_recordset.
123
+ def _get_model(from_internal_get_recordset: false)
124
+ return @model if instance_variable_defined?(:@model) && @model
125
+ return self.class.model if self.class.model
126
+ unless from_internal_get_recordset # prevent infinite recursion
127
+ recordset = self._get_recordset(from_internal_get_model: true)
128
+ return (@model = recordset.klass) if recordset
121
129
  end
122
-
123
- # Internal interface for get_recordset, protecting against infinite recursion with get_model.
124
- def _get_recordset(from_internal_get_model: false)
125
- return @recordset if instance_variable_defined?(:@recordset) && @recordset
126
- return self.class.recordset if self.class.recordset
127
- unless from_internal_get_model # prevent infinite recursion
128
- model = self._get_model(from_internal_get_recordset: true)
129
- return (@recordset = model.all) if model
130
- end
131
- return nil
130
+ begin
131
+ return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
132
+ rescue NameError
132
133
  end
134
+ return nil
135
+ end
133
136
 
134
- # Get the model for this controller.
135
- def get_model
136
- return _get_model
137
+ # Internal interface for get_recordset, protecting against infinite recursion with get_model.
138
+ def _get_recordset(from_internal_get_model: false)
139
+ return @recordset if instance_variable_defined?(:@recordset) && @recordset
140
+ return self.class.recordset if self.class.recordset
141
+ unless from_internal_get_model # prevent infinite recursion
142
+ model = self._get_model(from_internal_get_recordset: true)
143
+ return (@recordset = model.all) if model
137
144
  end
145
+ return nil
146
+ end
138
147
 
139
- # Get the set of records this controller has access to.
140
- def get_recordset
141
- return _get_recordset
142
- end
148
+ # Get the model for this controller.
149
+ def get_model
150
+ return _get_model
143
151
  end
144
152
 
145
- module ListModelMixin
146
- # TODO: pagination classes like Django
147
- def index
148
- @records = self.get_filtered_recordset
149
- @serialized_records = self.get_serializer_class.new(
150
- object: @records, controller: self
151
- ).serialize
152
- return api_response(@serialized_records)
153
- end
153
+ # Get the set of records this controller has access to.
154
+ def get_recordset
155
+ return _get_recordset
154
156
  end
157
+ end
158
+
155
159
 
156
- module ShowModelMixin
157
- def show
158
- @record = self.get_record
159
- @serialized_record = self.get_serializer_class.new(
160
- object: @record, controller: self
161
- ).serialize
162
- return api_response(@serialized_record)
160
+ # Mixin for listing records.
161
+ module RESTFramework::ListModelMixin
162
+ def index
163
+ @records = self.get_filtered_data(self.get_recordset)
164
+
165
+ # Handle pagination, if enabled.
166
+ if self.class.paginator_class
167
+ paginator = self.class.paginator_class.new(data: @records, controller: self)
168
+ page = paginator.get_page
169
+ serialized_page = self.get_serializer_class.new(object: page, controller: self).serialize
170
+ data = paginator.get_paginated_response(serialized_page)
171
+ else
172
+ data = self.get_serializer_class.new(object: @records, controller: self).serialize
163
173
  end
174
+
175
+ return api_response(data)
164
176
  end
177
+ end
165
178
 
166
- module CreateModelMixin
167
- def create
168
- @record = self.get_model.create!(self.get_create_params)
169
- @serialized_record = self.get_serializer_class.new(
170
- object: @record, controller: self
171
- ).serialize
172
- return api_response(@serialized_record)
173
- end
179
+
180
+ # Mixin for showing records.
181
+ module RESTFramework::ShowModelMixin
182
+ def show
183
+ @record = self.get_record
184
+ serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
185
+ return api_response(serialized_record)
174
186
  end
187
+ end
188
+
175
189
 
176
- module UpdateModelMixin
177
- def update
178
- @record = self.get_record
179
- @record.update!(self.get_update_params)
180
- @serialized_record = self.get_serializer_class.new(
181
- object: @record, controller: self
182
- ).serialize
183
- return api_response(@serialized_record)
190
+ # Mixin for creating records.
191
+ module RESTFramework::CreateModelMixin
192
+ def create
193
+ if self.get_recordset.respond_to?(:create!) && !self.disable_creation_from_recordset
194
+ # Create with any properties inherited from the recordset (like associations).
195
+ @record = self.get_recordset.create!(self.get_create_params)
196
+ else
197
+ # Otherwise, perform a "bare" create.
198
+ @record = self.get_model.create!(self.get_create_params)
184
199
  end
200
+ serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
201
+ return api_response(serialized_record)
185
202
  end
203
+ end
186
204
 
187
- module DestroyModelMixin
188
- def destroy
189
- @record = self.get_record
190
- @record.destroy!
191
- api_response(nil)
192
- end
205
+
206
+ # Mixin for updating records.
207
+ module RESTFramework::UpdateModelMixin
208
+ def update
209
+ @record = self.get_record
210
+ @record.update!(self.get_update_params)
211
+ serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
212
+ return api_response(serialized_record)
193
213
  end
214
+ end
194
215
 
195
- module ReadOnlyModelControllerMixin
196
- include BaseModelControllerMixin
197
- def self.included(base)
198
- if base.is_a? Class
199
- BaseModelControllerMixin.included(base)
200
- end
201
- end
202
216
 
203
- include ListModelMixin
204
- include ShowModelMixin
217
+ # Mixin for destroying records.
218
+ module RESTFramework::DestroyModelMixin
219
+ def destroy
220
+ @record = self.get_record
221
+ @record.destroy!
222
+ api_response(nil)
205
223
  end
224
+ end
206
225
 
207
- module ModelControllerMixin
208
- include BaseModelControllerMixin
209
- def self.included(base)
210
- if base.is_a? Class
211
- BaseModelControllerMixin.included(base)
212
- end
226
+
227
+ # Mixin that includes show/list mixins.
228
+ module RESTFramework::ReadOnlyModelControllerMixin
229
+ include RESTFramework::BaseModelControllerMixin
230
+
231
+ def self.included(base)
232
+ if base.is_a? Class
233
+ RESTFramework::BaseModelControllerMixin.included(base)
213
234
  end
235
+ end
236
+
237
+ include RESTFramework::ListModelMixin
238
+ include RESTFramework::ShowModelMixin
239
+ end
214
240
 
215
- include ListModelMixin
216
- include ShowModelMixin
217
- include CreateModelMixin
218
- include UpdateModelMixin
219
- include DestroyModelMixin
241
+
242
+ # Mixin that includes all the CRUD mixins.
243
+ module RESTFramework::ModelControllerMixin
244
+ include RESTFramework::BaseModelControllerMixin
245
+
246
+ def self.included(base)
247
+ if base.is_a? Class
248
+ RESTFramework::BaseModelControllerMixin.included(base)
249
+ end
220
250
  end
221
251
 
252
+ include RESTFramework::ListModelMixin
253
+ include RESTFramework::ShowModelMixin
254
+ include RESTFramework::CreateModelMixin
255
+ include RESTFramework::UpdateModelMixin
256
+ include RESTFramework::DestroyModelMixin
222
257
  end