rest_framework 0.0.13 → 0.1.1

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: 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