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