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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -0
  3. data/README.md +73 -45
  4. data/VERSION +1 -1
  5. data/app/views/layouts/rest_framework.html.erb +1 -1
  6. data/app/views/rest_framework/_head.html.erb +1 -2
  7. data/app/views/rest_framework/_heading.html.erb +1 -1
  8. data/app/views/rest_framework/_payloads.html.erb +1 -1
  9. data/app/views/rest_framework/_request_metadata.html.erb +2 -4
  10. data/app/views/rest_framework/_routes_and_forms.html.erb +2 -2
  11. data/lib/rest_framework/errors/base_error.rb +5 -0
  12. data/lib/rest_framework/errors/nil_passed_to_api_response_error.rb +14 -0
  13. data/lib/rest_framework/errors/unknown_model_error.rb +18 -0
  14. data/lib/rest_framework/errors.rb +4 -28
  15. data/lib/rest_framework/filters/{base.rb → base_filter.rb} +4 -1
  16. data/lib/rest_framework/filters/{model_ordering.rb → model_ordering_filter.rb} +4 -1
  17. data/lib/rest_framework/filters/{model_query.rb → model_query_filter.rb} +4 -1
  18. data/lib/rest_framework/filters/{model_search.rb → model_search_filter.rb} +4 -1
  19. data/lib/rest_framework/filters/{ransack.rb → ransack_filter.rb} +4 -1
  20. data/lib/rest_framework/filters.rb +5 -5
  21. data/lib/rest_framework/{controller_mixins/base.rb → mixins/base_controller_mixin.rb} +4 -5
  22. data/lib/rest_framework/{controller_mixins/bulk.rb → mixins/bulk_model_controller_mixin.rb} +16 -10
  23. data/lib/rest_framework/{controller_mixins/models.rb → mixins/model_controller_mixin.rb} +34 -53
  24. data/lib/rest_framework/mixins.rb +7 -0
  25. data/lib/rest_framework/paginators/base_paginator.rb +19 -0
  26. data/lib/rest_framework/paginators/page_number_paginator.rb +84 -0
  27. data/lib/rest_framework/paginators.rb +3 -84
  28. data/lib/rest_framework/routers.rb +0 -1
  29. data/lib/rest_framework/serializers/active_model_serializer_adapter_factory.rb +20 -0
  30. data/lib/rest_framework/serializers/base_serializer.rb +40 -0
  31. data/lib/rest_framework/serializers/native_serializer.rb +360 -0
  32. data/lib/rest_framework/serializers.rb +4 -383
  33. data/lib/rest_framework.rb +1 -1
  34. metadata +20 -12
  35. 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. The return value is cached and exposed 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 @recordset if instance_variable_defined?(:@recordset)
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 (model = self.class.get_model)
563
- return @recordset = model.all
557
+ if model = self.class.get_model
558
+ return model.all
564
559
  end
565
560
 
566
- return @recordset = nil
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 if instance_variable_defined?(:@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 instance_variable_defined?(:@record)
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,7 @@
1
+ module RESTFramework::Mixins
2
+ end
3
+
4
+ require_relative "mixins/base_controller_mixin"
5
+
6
+ require_relative "mixins/bulk_model_controller_mixin"
7
+ require_relative "mixins/model_controller_mixin"
@@ -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
- class RESTFramework::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
1
+ module RESTFramework::Paginators
16
2
  end
17
3
 
18
- # A simple paginator based on page numbers.
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
- # Ensure we don't exceed the max page size.
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
@@ -1,5 +1,4 @@
1
1
  require "action_dispatch/routing/mapper"
2
- require_relative "utils"
3
2
 
4
3
  module ActionDispatch::Routing
5
4
  class Mapper
@@ -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