rest_framework 0.9.7 → 0.9.9
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 +4 -4
- data/.yardopts +5 -0
- data/README.md +73 -45
- data/VERSION +1 -1
- data/app/views/layouts/rest_framework.html.erb +1 -1
- data/app/views/rest_framework/_head.html.erb +1 -2
- data/app/views/rest_framework/_heading.html.erb +1 -1
- data/app/views/rest_framework/_payloads.html.erb +1 -1
- data/app/views/rest_framework/_request_metadata.html.erb +2 -4
- data/app/views/rest_framework/_routes_and_forms.html.erb +2 -2
- data/lib/rest_framework/errors/base_error.rb +5 -0
- data/lib/rest_framework/errors/nil_passed_to_api_response_error.rb +14 -0
- data/lib/rest_framework/errors/unknown_model_error.rb +18 -0
- data/lib/rest_framework/errors.rb +4 -28
- data/lib/rest_framework/filters/{base.rb → base_filter.rb} +4 -1
- data/lib/rest_framework/filters/{model_ordering.rb → model_ordering_filter.rb} +4 -1
- data/lib/rest_framework/filters/{model_query.rb → model_query_filter.rb} +4 -1
- data/lib/rest_framework/filters/{model_search.rb → model_search_filter.rb} +4 -1
- data/lib/rest_framework/filters/{ransack.rb → ransack_filter.rb} +4 -1
- data/lib/rest_framework/filters.rb +5 -5
- data/lib/rest_framework/{controller_mixins/base.rb → mixins/base_controller_mixin.rb} +4 -5
- data/lib/rest_framework/{controller_mixins/bulk.rb → mixins/bulk_model_controller_mixin.rb} +16 -10
- data/lib/rest_framework/{controller_mixins/models.rb → mixins/model_controller_mixin.rb} +34 -53
- data/lib/rest_framework/mixins.rb +7 -0
- data/lib/rest_framework/paginators/base_paginator.rb +19 -0
- data/lib/rest_framework/paginators/page_number_paginator.rb +84 -0
- data/lib/rest_framework/paginators.rb +3 -84
- data/lib/rest_framework/routers.rb +0 -1
- data/lib/rest_framework/serializers/active_model_serializer_adapter_factory.rb +20 -0
- data/lib/rest_framework/serializers/base_serializer.rb +40 -0
- data/lib/rest_framework/serializers/native_serializer.rb +360 -0
- data/lib/rest_framework/serializers.rb +4 -383
- data/lib/rest_framework.rb +1 -1
- metadata +20 -12
- data/lib/rest_framework/controller_mixins.rb +0 -7
@@ -1,8 +1,5 @@
|
|
1
|
-
require_relative "base"
|
2
|
-
require_relative "../filters"
|
3
|
-
|
4
1
|
# This module provides the core functionality for controllers based on models.
|
5
|
-
module RESTFramework::BaseModelControllerMixin
|
2
|
+
module RESTFramework::Mixins::BaseModelControllerMixin
|
6
3
|
BASE64_REGEX = /data:(.*);base64,(.*)/
|
7
4
|
BASE64_TRANSLATE = ->(field, value) {
|
8
5
|
_, content_type, payload = value.match(BASE64_REGEX).to_a
|
@@ -552,54 +549,28 @@ module RESTFramework::BaseModelControllerMixin
|
|
552
549
|
alias_method :get_create_params, :get_body_params
|
553
550
|
alias_method :get_update_params, :get_body_params
|
554
551
|
|
555
|
-
# Get the set of records this controller has access to.
|
556
|
-
# the view as the `@recordset` instance variable.
|
552
|
+
# Get the set of records this controller has access to.
|
557
553
|
def get_recordset
|
558
|
-
return
|
559
|
-
return (@recordset = self.class.recordset) if self.class.recordset
|
554
|
+
return self.class.recordset if self.class.recordset
|
560
555
|
|
561
556
|
# If there is a model, return that model's default scope (all records by default).
|
562
|
-
if
|
563
|
-
return
|
557
|
+
if model = self.class.get_model
|
558
|
+
return model.all
|
564
559
|
end
|
565
560
|
|
566
|
-
return
|
567
|
-
end
|
568
|
-
|
569
|
-
# Get the recordset but with any associations included to avoid N+1 queries.
|
570
|
-
def get_recordset_with_includes
|
571
|
-
reflections = self.class.get_model.reflections
|
572
|
-
associations = self.get_fields.map { |f|
|
573
|
-
if reflections.key?(f)
|
574
|
-
f.to_sym
|
575
|
-
elsif reflections.key?("rich_text_#{f}")
|
576
|
-
:"rich_text_#{f}"
|
577
|
-
elsif reflections.key?("#{f}_attachment")
|
578
|
-
:"#{f}_attachment"
|
579
|
-
elsif reflections.key?("#{f}_attachments")
|
580
|
-
:"#{f}_attachments"
|
581
|
-
end
|
582
|
-
}.compact
|
583
|
-
|
584
|
-
if associations.any?
|
585
|
-
return self.get_recordset.includes(associations)
|
586
|
-
end
|
587
|
-
|
588
|
-
return self.get_recordset
|
561
|
+
return nil
|
589
562
|
end
|
590
563
|
|
591
564
|
# Get the records this controller has access to *after* any filtering is applied.
|
592
565
|
def get_records
|
593
|
-
return @records
|
594
|
-
|
595
|
-
return @records = self.get_filtered_data(self.get_recordset_with_includes)
|
566
|
+
return @records ||= self.get_filtered_data(self.get_recordset)
|
596
567
|
end
|
597
568
|
|
598
569
|
# Get a single record by primary key or another column, if allowed. The return value is cached and
|
599
570
|
# exposed to the view as the `@record` instance variable.
|
600
571
|
def get_record
|
601
572
|
# Cache the result.
|
602
|
-
return @record if
|
573
|
+
return @record if @record
|
603
574
|
|
604
575
|
recordset = self.get_recordset
|
605
576
|
find_by_key = self.class.get_model.primary_key
|
@@ -661,7 +632,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
661
632
|
end
|
662
633
|
|
663
634
|
# Mixin for listing records.
|
664
|
-
module RESTFramework::ListModelMixin
|
635
|
+
module RESTFramework::Mixins::ListModelMixin
|
665
636
|
def index
|
666
637
|
return api_response(self.get_index_records)
|
667
638
|
end
|
@@ -689,14 +660,14 @@ module RESTFramework::ListModelMixin
|
|
689
660
|
end
|
690
661
|
|
691
662
|
# Mixin for showing records.
|
692
|
-
module RESTFramework::ShowModelMixin
|
663
|
+
module RESTFramework::Mixins::ShowModelMixin
|
693
664
|
def show
|
694
665
|
return api_response(self.get_record)
|
695
666
|
end
|
696
667
|
end
|
697
668
|
|
698
669
|
# Mixin for creating records.
|
699
|
-
module RESTFramework::CreateModelMixin
|
670
|
+
module RESTFramework::Mixins::CreateModelMixin
|
700
671
|
def create
|
701
672
|
return api_response(self.create!, status: :created)
|
702
673
|
end
|
@@ -708,7 +679,7 @@ module RESTFramework::CreateModelMixin
|
|
708
679
|
end
|
709
680
|
|
710
681
|
# Mixin for updating records.
|
711
|
-
module RESTFramework::UpdateModelMixin
|
682
|
+
module RESTFramework::Mixins::UpdateModelMixin
|
712
683
|
def update
|
713
684
|
return api_response(self.update!)
|
714
685
|
end
|
@@ -722,7 +693,7 @@ module RESTFramework::UpdateModelMixin
|
|
722
693
|
end
|
723
694
|
|
724
695
|
# Mixin for destroying records.
|
725
|
-
module RESTFramework::DestroyModelMixin
|
696
|
+
module RESTFramework::Mixins::DestroyModelMixin
|
726
697
|
def destroy
|
727
698
|
self.destroy!
|
728
699
|
return api_response("")
|
@@ -735,11 +706,11 @@ module RESTFramework::DestroyModelMixin
|
|
735
706
|
end
|
736
707
|
|
737
708
|
# Mixin that includes show/list mixins.
|
738
|
-
module RESTFramework::ReadOnlyModelControllerMixin
|
739
|
-
include RESTFramework::BaseModelControllerMixin
|
709
|
+
module RESTFramework::Mixins::ReadOnlyModelControllerMixin
|
710
|
+
include RESTFramework::Mixins::BaseModelControllerMixin
|
740
711
|
|
741
|
-
include RESTFramework::ListModelMixin
|
742
|
-
include RESTFramework::ShowModelMixin
|
712
|
+
include RESTFramework::Mixins::ListModelMixin
|
713
|
+
include RESTFramework::Mixins::ShowModelMixin
|
743
714
|
|
744
715
|
def self.included(base)
|
745
716
|
RESTFramework::BaseModelControllerMixin.included(base)
|
@@ -747,16 +718,26 @@ module RESTFramework::ReadOnlyModelControllerMixin
|
|
747
718
|
end
|
748
719
|
|
749
720
|
# Mixin that includes all the CRUD mixins.
|
750
|
-
module RESTFramework::ModelControllerMixin
|
751
|
-
include RESTFramework::BaseModelControllerMixin
|
721
|
+
module RESTFramework::Mixins::ModelControllerMixin
|
722
|
+
include RESTFramework::Mixins::BaseModelControllerMixin
|
752
723
|
|
753
|
-
include RESTFramework::ListModelMixin
|
754
|
-
include RESTFramework::ShowModelMixin
|
755
|
-
include RESTFramework::CreateModelMixin
|
756
|
-
include RESTFramework::UpdateModelMixin
|
757
|
-
include RESTFramework::DestroyModelMixin
|
724
|
+
include RESTFramework::Mixins::ListModelMixin
|
725
|
+
include RESTFramework::Mixins::ShowModelMixin
|
726
|
+
include RESTFramework::Mixins::CreateModelMixin
|
727
|
+
include RESTFramework::Mixins::UpdateModelMixin
|
728
|
+
include RESTFramework::Mixins::DestroyModelMixin
|
758
729
|
|
759
730
|
def self.included(base)
|
760
731
|
RESTFramework::BaseModelControllerMixin.included(base)
|
761
732
|
end
|
762
733
|
end
|
734
|
+
|
735
|
+
# Aliases for convenience.
|
736
|
+
RESTFramework::BaseModelControllerMixin = RESTFramework::Mixins::BaseModelControllerMixin
|
737
|
+
RESTFramework::ListModelMixin = RESTFramework::Mixins::ListModelMixin
|
738
|
+
RESTFramework::ShowModelMixin = RESTFramework::Mixins::ShowModelMixin
|
739
|
+
RESTFramework::CreateModelMixin = RESTFramework::Mixins::CreateModelMixin
|
740
|
+
RESTFramework::UpdateModelMixin = RESTFramework::Mixins::UpdateModelMixin
|
741
|
+
RESTFramework::DestroyModelMixin = RESTFramework::Mixins::DestroyModelMixin
|
742
|
+
RESTFramework::ReadOnlyModelControllerMixin = RESTFramework::Mixins::ReadOnlyModelControllerMixin
|
743
|
+
RESTFramework::ModelControllerMixin = RESTFramework::Mixins::ModelControllerMixin
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class RESTFramework::Paginators::BasePaginator
|
2
|
+
def initialize(data:, controller:, **kwargs)
|
3
|
+
@data = data
|
4
|
+
@controller = controller
|
5
|
+
end
|
6
|
+
|
7
|
+
# Get the page and return it so the caller can serialize it.
|
8
|
+
def get_page
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
# Wrap the serialized page with appropriate metadata.
|
13
|
+
def get_paginated_response(serialized_page)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Alias for convenience.
|
19
|
+
RESTFramework::BasePaginator = RESTFramework::Paginators::BasePaginator
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# A simple paginator based on page numbers.
|
2
|
+
#
|
3
|
+
# Example: http://example.com/api/users/?page=3&page_size=50
|
4
|
+
class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators::BasePaginator
|
5
|
+
def initialize(**kwargs)
|
6
|
+
super
|
7
|
+
# Exclude any `select` clauses since that would cause `count` to fail with a SQL `SyntaxError`.
|
8
|
+
@count = @data.except(:select).count
|
9
|
+
@page_size = self._page_size
|
10
|
+
|
11
|
+
@total_pages = @count / @page_size
|
12
|
+
@total_pages += 1 if @count % @page_size != 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def _page_size
|
16
|
+
page_size = nil
|
17
|
+
|
18
|
+
# Get from context, if allowed.
|
19
|
+
if @controller.page_size_query_param
|
20
|
+
if page_size = @controller.params[@controller.page_size_query_param].presence
|
21
|
+
page_size = page_size.to_i
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Otherwise, get from config.
|
26
|
+
if !page_size && @controller.page_size
|
27
|
+
page_size = @controller.page_size
|
28
|
+
end
|
29
|
+
|
30
|
+
# Ensure we don't exceed the max page size.
|
31
|
+
if @controller.max_page_size && page_size > @controller.max_page_size
|
32
|
+
page_size = @controller.max_page_size
|
33
|
+
end
|
34
|
+
|
35
|
+
# Ensure we return at least 1.
|
36
|
+
return page_size.zero? ? 1 : page_size
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the page and return it so the caller can serialize it.
|
40
|
+
def get_page(page_number=nil)
|
41
|
+
# If page number isn't provided, infer from the params or use 1 as a fallback value.
|
42
|
+
unless page_number
|
43
|
+
page_number = @controller&.params&.[](@controller.page_query_param&.to_sym)
|
44
|
+
if page_number.blank?
|
45
|
+
page_number = 1
|
46
|
+
else
|
47
|
+
page_number = page_number.to_i
|
48
|
+
if page_number.zero?
|
49
|
+
page_number = 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@page_number = page_number
|
54
|
+
|
55
|
+
# Get the data page and return it so the caller can serialize the data in the proper format.
|
56
|
+
page_index = @page_number - 1
|
57
|
+
return @data.limit(@page_size).offset(page_index * @page_size)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Wrap the serialized page with appropriate metadata. TODO: include links.
|
61
|
+
def get_paginated_response(serialized_page)
|
62
|
+
page_query_param = @controller.page_query_param
|
63
|
+
base_params = @controller.params.to_unsafe_h
|
64
|
+
next_url = if @page_number < @total_pages
|
65
|
+
@controller.url_for({**base_params, page_query_param => @page_number + 1})
|
66
|
+
end
|
67
|
+
previous_url = if @page_number > 1
|
68
|
+
@controller.url_for({**base_params, page_query_param => @page_number - 1})
|
69
|
+
end
|
70
|
+
|
71
|
+
return {
|
72
|
+
count: @count,
|
73
|
+
page: @page_number,
|
74
|
+
page_size: @page_size,
|
75
|
+
total_pages: @total_pages,
|
76
|
+
next: next_url,
|
77
|
+
previous: previous_url,
|
78
|
+
results: serialized_page,
|
79
|
+
}.compact
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Alias for convenience.
|
84
|
+
RESTFramework::PageNumberPaginator = RESTFramework::Paginators::PageNumberPaginator
|
@@ -1,90 +1,9 @@
|
|
1
|
-
|
2
|
-
def initialize(data:, controller:, **kwargs)
|
3
|
-
@data = data
|
4
|
-
@controller = controller
|
5
|
-
end
|
6
|
-
|
7
|
-
# Get the page and return it so the caller can serialize it.
|
8
|
-
def get_page
|
9
|
-
raise NotImplementedError
|
10
|
-
end
|
11
|
-
|
12
|
-
# Wrap the serialized page with appropriate metadata.
|
13
|
-
def get_paginated_response(serialized_page)
|
14
|
-
raise NotImplementedError
|
15
|
-
end
|
1
|
+
module RESTFramework::Paginators
|
16
2
|
end
|
17
3
|
|
18
|
-
|
19
|
-
#
|
20
|
-
# Example: http://example.com/api/users/?page=3&page_size=50
|
21
|
-
class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
|
22
|
-
def initialize(**kwargs)
|
23
|
-
super
|
24
|
-
# Exclude any `select` clauses since that would cause `count` to fail with a SQL `SyntaxError`.
|
25
|
-
@count = @data.except(:select).count
|
26
|
-
@page_size = self._page_size
|
27
|
-
|
28
|
-
@total_pages = @count / @page_size
|
29
|
-
@total_pages += 1 if @count % @page_size != 0
|
30
|
-
end
|
31
|
-
|
32
|
-
def _page_size
|
33
|
-
page_size = nil
|
34
|
-
|
35
|
-
# Get from context, if allowed.
|
36
|
-
if @controller.page_size_query_param
|
37
|
-
if page_size = @controller.params[@controller.page_size_query_param].presence
|
38
|
-
page_size = page_size.to_i
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Otherwise, get from config.
|
43
|
-
if !page_size && @controller.page_size
|
44
|
-
page_size = @controller.page_size
|
45
|
-
end
|
4
|
+
require_relative "paginators/base_paginator"
|
46
5
|
|
47
|
-
|
48
|
-
if @controller.max_page_size && page_size > @controller.max_page_size
|
49
|
-
page_size = @controller.max_page_size
|
50
|
-
end
|
51
|
-
|
52
|
-
# Ensure we return at least 1.
|
53
|
-
return page_size.zero? ? 1 : page_size
|
54
|
-
end
|
55
|
-
|
56
|
-
# Get the page and return it so the caller can serialize it.
|
57
|
-
def get_page(page_number=nil)
|
58
|
-
# If page number isn't provided, infer from the params or use 1 as a fallback value.
|
59
|
-
unless page_number
|
60
|
-
page_number = @controller&.params&.[](@controller.page_query_param&.to_sym)
|
61
|
-
if page_number.blank?
|
62
|
-
page_number = 1
|
63
|
-
else
|
64
|
-
page_number = page_number.to_i
|
65
|
-
if page_number.zero?
|
66
|
-
page_number = 1
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
@page_number = page_number
|
71
|
-
|
72
|
-
# Get the data page and return it so the caller can serialize the data in the proper format.
|
73
|
-
page_index = @page_number - 1
|
74
|
-
return @data.limit(@page_size).offset(page_index * @page_size)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Wrap the serialized page with appropriate metadata. TODO: include links.
|
78
|
-
def get_paginated_response(serialized_page)
|
79
|
-
return {
|
80
|
-
count: @count,
|
81
|
-
page: @page_number,
|
82
|
-
page_size: @page_size,
|
83
|
-
total_pages: @total_pages,
|
84
|
-
results: serialized_page,
|
85
|
-
}
|
86
|
-
end
|
87
|
-
end
|
6
|
+
require_relative "paginators/page_number_paginator"
|
88
7
|
|
89
8
|
# TODO: implement this
|
90
9
|
# class RESTFramework::CountOffsetPaginator
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This is a helper factory to wrap an ActiveModelSerializer to provide a `serialize` method which
|
2
|
+
# accepts both collections and individual records. Use `.for` to build adapters.
|
3
|
+
class RESTFramework::Serializers::ActiveModelSerializerAdapterFactory
|
4
|
+
def self.for(active_model_serializer)
|
5
|
+
return Class.new(active_model_serializer) do
|
6
|
+
def serialize
|
7
|
+
if self.object.respond_to?(:to_ary)
|
8
|
+
return self.object.map { |r| self.class.superclass.new(r).serializable_hash }
|
9
|
+
end
|
10
|
+
|
11
|
+
return self.serializable_hash
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Alias for convenience.
|
18
|
+
# rubocop:disable Layout/LineLength
|
19
|
+
RESTFramework::ActiveModelSerializerAdapterFactory = RESTFramework::Serializers::ActiveModelSerializerAdapterFactory
|
20
|
+
# rubocop:enable Layout/LineLength
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# The base serializer defines the interface for all REST Framework serializers.
|
2
|
+
class RESTFramework::Serializers::BaseSerializer
|
3
|
+
# Add `object` accessor to be compatible with `ActiveModel::Serializer`.
|
4
|
+
attr_accessor :object
|
5
|
+
|
6
|
+
# Accept/ignore `*args` to be compatible with the `ActiveModel::Serializer#initialize` signature.
|
7
|
+
def initialize(object=nil, *args, controller: nil, **kwargs)
|
8
|
+
@object = object
|
9
|
+
@controller = controller
|
10
|
+
end
|
11
|
+
|
12
|
+
# The primary interface for extracting a native Ruby types. This works both for records and
|
13
|
+
# collections. We accept and ignore `*args` for compatibility with `active_model_serializers`.
|
14
|
+
def serialize(*args)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
# Synonym for `serialize` for compatibility with `active_model_serializers`.
|
19
|
+
def serializable_hash(*args)
|
20
|
+
return self.serialize(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
# For compatibility with `active_model_serializers`.
|
24
|
+
def self.cache_enabled?
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
# For compatibility with `active_model_serializers`.
|
29
|
+
def self.fragment_cache_enabled?
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
# For compatibility with `active_model_serializers`.
|
34
|
+
def associations(*args, **kwargs)
|
35
|
+
return []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Alias for convenience.
|
40
|
+
RESTFramework::BaseSerializer = RESTFramework::Serializers::BaseSerializer
|