rest_framework 0.9.7 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|