morpheus 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.rspec +1 -1
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +1 -1
  4. data/lib/morpheus.rb +3 -23
  5. data/lib/morpheus/base.rb +11 -11
  6. data/lib/morpheus/client.rb +10 -0
  7. data/lib/morpheus/client/associations.rb +6 -6
  8. data/lib/morpheus/client/cached_request_formatter.rb +20 -0
  9. data/lib/morpheus/client/log_subscriber.rb +2 -28
  10. data/lib/morpheus/client/railtie.rb +4 -4
  11. data/lib/morpheus/client/request_formatter.rb +50 -0
  12. data/lib/morpheus/client/uncached_request_formatter.rb +40 -0
  13. data/lib/morpheus/configuration.rb +30 -9
  14. data/lib/morpheus/mixins.rb +16 -0
  15. data/lib/morpheus/mixins/associations.rb +39 -37
  16. data/lib/morpheus/mixins/associations/association.rb +112 -0
  17. data/lib/morpheus/mixins/associations/belongs_to_association.rb +47 -0
  18. data/lib/morpheus/mixins/associations/has_many_association.rb +72 -0
  19. data/lib/morpheus/mixins/associations/has_one_association.rb +48 -0
  20. data/lib/morpheus/mixins/attributes.rb +97 -95
  21. data/lib/morpheus/mixins/conversion.rb +16 -14
  22. data/lib/morpheus/mixins/filtering.rb +13 -11
  23. data/lib/morpheus/mixins/finders.rb +47 -45
  24. data/lib/morpheus/mixins/introspection.rb +15 -13
  25. data/lib/morpheus/mixins/persistence.rb +32 -30
  26. data/lib/morpheus/mixins/reflections.rb +17 -15
  27. data/lib/morpheus/mixins/request_handling.rb +27 -25
  28. data/lib/morpheus/mixins/response_parsing.rb +10 -8
  29. data/lib/morpheus/mixins/url_support.rb +27 -25
  30. data/lib/morpheus/reflection.rb +5 -2
  31. data/lib/morpheus/type_caster.rb +1 -1
  32. data/lib/morpheus/version.rb +1 -1
  33. data/morpheus.gemspec +2 -2
  34. data/spec/dummy/app/resources/book.rb +1 -1
  35. data/spec/morpheus/base_spec.rb +35 -35
  36. data/spec/morpheus/client/cached_request_formatter_spec.rb +28 -0
  37. data/spec/morpheus/client/log_subscriber_spec.rb +53 -10
  38. data/spec/morpheus/client/request_formatter_spec.rb +5 -0
  39. data/spec/morpheus/client/uncached_request_formatter_spec.rb +29 -0
  40. data/spec/morpheus/configuration_spec.rb +49 -11
  41. data/spec/morpheus/{associations → mixins/associations}/association_spec.rb +1 -1
  42. data/spec/morpheus/{associations → mixins/associations}/belongs_to_association_spec.rb +1 -1
  43. data/spec/morpheus/{associations → mixins/associations}/has_many_association_spec.rb +1 -1
  44. data/spec/morpheus/{associations → mixins/associations}/has_one_association_spec.rb +1 -1
  45. data/spec/morpheus/mixins/associations_spec.rb +1 -1
  46. data/spec/morpheus/mixins/attributes_spec.rb +27 -6
  47. data/spec/morpheus/mixins/conversion_spec.rb +1 -1
  48. data/spec/morpheus/mixins/filtering_spec.rb +2 -2
  49. data/spec/morpheus/mixins/finders_spec.rb +1 -1
  50. data/spec/morpheus/mixins/introspection_spec.rb +1 -1
  51. data/spec/morpheus/mixins/persistence_spec.rb +1 -1
  52. data/spec/morpheus/mixins/reflections_spec.rb +1 -1
  53. data/spec/morpheus/mixins/request_handling_spec.rb +2 -2
  54. data/spec/morpheus/mixins/response_parsing_spec.rb +2 -2
  55. data/spec/morpheus/mixins/url_support_spec.rb +2 -2
  56. data/spec/morpheus/response_parser_spec.rb +5 -1
  57. data/spec/regressions/sorting_resources_spec.rb +119 -0
  58. data/spec/spec_helper.rb +3 -2
  59. metadata +159 -87
  60. data/lib/morpheus/associations/association.rb +0 -110
  61. data/lib/morpheus/associations/belongs_to_association.rb +0 -45
  62. data/lib/morpheus/associations/has_many_association.rb +0 -70
  63. data/lib/morpheus/associations/has_one_association.rb +0 -46
@@ -1,110 +0,0 @@
1
- # The Association class serves as the basis for associating resources.
2
- # Resources have an association DSL that follows that of ActiveRecord:
3
- #
4
- # has_many :automobiles
5
- #
6
- # has_one :car
7
- #
8
- # belongs_to :car_club
9
- #
10
- # This class acts as a proxy that will allow for lazy evaluation of an
11
- # association. The proxy waits to make the request for the object until
12
- # a method on the association is called. When this happens, because the
13
- # method does not exist on the proxy object, method_missing will be
14
- # called, the target object will be loaded, and then the method will be
15
- # called on the loaded target.
16
- module Morpheus
17
- module Associations
18
- class Association < ActiveSupport::BasicObject
19
-
20
- # Associations can be loaded with several options.
21
- def initialize(owner, association, settings = {})
22
- # @owner stores the class that the association exists on.
23
- @owner = owner
24
-
25
- # @association stores the associated class, as named in the association
26
- # method (i.e. :automobile, :car, :car_club)
27
- @association = association
28
-
29
- # @target stores the loaded object. It is not typically accessed directly,
30
- # but instead should be accessed through the loaded_target method.
31
- @target = settings[:target]
32
-
33
- @filters = settings[:filters] || []
34
-
35
- @includes = []
36
-
37
- # @options holds the chosen options for the association. Several of these
38
- # options are set in the subclass' initializer.
39
- @options = settings[:options] || {}
40
-
41
- # In some cases, the association name will not match that of the class
42
- # that should be instantiated when it is invoked. Here, we can specify
43
- # that this association uses a specified class as its target. When the
44
- # request is made for the association, this class will be used to
45
- # instantiate this object or collection.
46
- @options[:class_name] = settings[:options][:class_name] || @association.to_s.classify
47
- end
48
-
49
- # The proxy implements a few methods that need to be delegated to the target
50
- # so that they will work as expected.
51
- def id
52
- loaded_target.id
53
- end
54
-
55
- def nil?
56
- loaded_target.nil?
57
- end
58
-
59
- def to_param
60
- loaded_target.to_param
61
- end
62
-
63
- def try(method, *args, &block)
64
- loaded_target.try(method, *args, &block)
65
- end
66
-
67
- def includes(*associations)
68
- associations.each do |association|
69
- @includes << association unless @includes.include?(association)
70
- end
71
- self
72
- end
73
-
74
- # This is left to be implemented by the subclasses as it will operate
75
- # differently in each case.
76
- def load_target!
77
- end
78
-
79
- private
80
-
81
- # The loaded_target method holds a cached version of the loaded target.
82
- # This method is used to access the proxied object. Since a request will
83
- # be made when a method is invoked on the object, and this can happen
84
- # very often, we are caching the target here, so that only a single
85
- # request will be made.
86
- def loaded_target
87
- @target ||= load_target!
88
- if ::Array === @target && !@filters.empty?
89
- @filters.uniq.inject(@target.dup) do |target, filter|
90
- filter.call(target)
91
- end
92
- else
93
- @target
94
- end
95
- end
96
-
97
- # The method_missing hook will be called when methods that do not exist
98
- # on the proxy object are invoked. This is the point at which the proxied
99
- # object is loaded, if it has not been loaded already.
100
- def method_missing(m, *args, &block)
101
- if filter = @association_class.find_filter(m)
102
- with_filter(filter)
103
- else
104
- loaded_target.send(m, *args, &block)
105
- end
106
- end
107
-
108
- end
109
- end
110
- end
@@ -1,45 +0,0 @@
1
- module Morpheus
2
- module Associations
3
- class BelongsToAssociation < Association
4
-
5
- # The initializer calls out to the superclass' initializer, then will set the
6
- # options particular to itself.
7
- def initialize(owner, association, settings = {})
8
- super
9
-
10
- # The foreign key is used to generate the url for a request. The default uses
11
- # the association name with an '_id' suffix as the generated key. For example,
12
- # belongs_to :school, will have the foreign key :school_id.
13
- @options[:foreign_key] ||= "#{@association.to_s}_id".to_sym
14
-
15
- # The primary key defaults to :id.
16
- @options[:primary_key] ||= :id
17
-
18
- # Associations can be marked as polymorphic. These associations will use
19
- # the returned type to instantiate the associated object.
20
- @options[:polymorphic] = settings[:options][:polymorphic] || false
21
-
22
- # @association_class stores the class of the association, constantized
23
- # from the named association (i.e. Automobile, Car, CarClub)
24
- @association_class = @options[:class_name].constantize
25
- end
26
-
27
- def with_filter(filter)
28
- BelongsToAssociation.new(@owner, @association, :target => @target, :filters => @filters.dup.push(filter), :options => @options)
29
- end
30
-
31
- # When loading the target, the association will only be loaded if the foreign_key
32
- # has been set. Additionally, the class used to find the record will be inferred
33
- # by calling the method which is the name of the association with a '_type' suffix.
34
- # Alternatively, the class name can be set by using the :class_name option.
35
- def load_target!
36
- if association_id = @owner.send(@options[:foreign_key])
37
- polymorphic_class = @options[:polymorphic] ? @owner.send("#{@association}_type".to_sym).constantize : @options[:class_name].constantize
38
- attributes = [UrlBuilder.belongs_to(polymorphic_class, association_id), nil, { :id => association_id }]
39
- polymorphic_class.find(association_id)
40
- end
41
- end
42
-
43
- end
44
- end
45
- end
@@ -1,70 +0,0 @@
1
- module Morpheus
2
- module Associations
3
- class HasManyAssociation < Association
4
-
5
- # The initializer calls out to the superclass' initializer and then
6
- # sets the options particular to itself.
7
- def initialize(owner, association, settings = {})
8
- super
9
-
10
- # The foreign key is used to generate the url for the association
11
- # request when the association is transformed into a relation.
12
- # The default is to use the class of the owner object with an '_id'
13
- # suffix.
14
- @options[:foreign_key] ||= "#{@owner.class.to_s.underscore}_id".to_sym
15
-
16
- # The primary key is used in the generated url for the target. It
17
- # defaults to :id.
18
- @options[:primary_key] ||= :id
19
-
20
- # @association_class stores the class of the association, constantized
21
- # from the named association (i.e. Automobile, Car, CarClub)
22
- @association_class = @options[:class_name].constantize
23
- end
24
-
25
- def with_filter(filter)
26
- HasManyAssociation.new(@owner, @association, :target => @target, :filters => @filters.dup.push(filter), :options => @options)
27
- end
28
-
29
- # When loading the target, the primary key is first checked. If the
30
- # key is nil, then an empty array is returned. Otherwise, the target
31
- # is requested at the generated url. For a has_many :meetings
32
- # association on a class called Course, the generated url might look
33
- # like this: /meetings?course_id=1, where the 1 is the primary key.
34
- def load_target!
35
- if primary_key = @owner.send(@options[:primary_key])
36
- Relation.new(@association.to_s.classify.constantize).where(@options[:foreign_key] => primary_key).all
37
- else
38
- []
39
- end
40
- end
41
-
42
- # The where, limit, and page methods delegate to a Relation object.
43
- # The association generates a relation, and then calls the very
44
- # same where, limit, or page method on that relation object.
45
- def where(query_attributes)
46
- transform_association_into_relation.where(query_attributes)
47
- end
48
-
49
- def limit(amount)
50
- transform_association_into_relation.limit(amount)
51
- end
52
-
53
- def page(page_number)
54
- transform_association_into_relation.page(page_number)
55
- end
56
-
57
- def transform_association_into_relation
58
- Relation.new(@association.to_s.classify.constantize).where(@options[:foreign_key] => @owner.send(@options[:primary_key]))
59
- end
60
- private :transform_association_into_relation
61
-
62
- # The append operator is used to append new resources to the association.
63
- def <<(one_of_many)
64
- @target ||= []
65
- @target << one_of_many
66
- end
67
-
68
- end
69
- end
70
- end
@@ -1,46 +0,0 @@
1
- module Morpheus
2
- module Associations
3
- class HasOneAssociation < Association
4
-
5
- # The initializer calls out to the superclass' initializer and then
6
- # sets the options particular to itself.
7
- def initialize(owner, association, settings = {})
8
- super
9
-
10
- # The foreign key is used to generate the url for the association
11
- # request when the association is transformed into a relation.
12
- # The default is to use the class of the owner object with an '_id'
13
- # suffix.
14
- @options[:foreign_key] ||= "#{@owner.class.to_s.underscore}_id".to_sym
15
-
16
- # The primary key is used in the generated url for the target. It
17
- # defaults to :id.
18
- @options[:primary_key] ||= :id
19
-
20
- # @association_class stores the class of the association, constantized
21
- # from the named association (i.e. Automobile, Car, CarClub)
22
- if @options[:class_name]
23
- @association_class = @options[:class_name].constantize
24
- else
25
- @association_class = @association
26
- end
27
- end
28
-
29
- def with_filter(filter)
30
- HasOneAssociation.new(@owner, @association, :target => @target, :filters => @filters.dup.push(filter), :options => @options)
31
- end
32
-
33
- # When loading the target, the primary key is first checked. If the
34
- # key is nil, then an nil is returned. Otherwise, the target
35
- # is requested at the generated url. For a has_one :meeting
36
- # association on a class called Course, the generated url might look
37
- # like this: /meetings?course_id=1, where the 1 is the primary key.
38
- def load_target!
39
- if primary_key = @owner.send(@options[:primary_key])
40
- Relation.new(@association_class.to_s.classify.constantize).where(@options[:foreign_key] => primary_key).limit(1).first
41
- end
42
- end
43
-
44
- end
45
- end
46
- end