morpheus 0.3.4

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 (108) hide show
  1. data/.rvmrc +1 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +134 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +44 -0
  6. data/Rakefile +48 -0
  7. data/VERSION +1 -0
  8. data/autotest/discover.rb +7 -0
  9. data/lib/ext/typhoeus.rb +37 -0
  10. data/lib/morpheus/associations/association.rb +110 -0
  11. data/lib/morpheus/associations/belongs_to_association.rb +45 -0
  12. data/lib/morpheus/associations/has_many_association.rb +70 -0
  13. data/lib/morpheus/associations/has_one_association.rb +46 -0
  14. data/lib/morpheus/base.rb +66 -0
  15. data/lib/morpheus/client/associations.rb +47 -0
  16. data/lib/morpheus/client/inflections.rb +3 -0
  17. data/lib/morpheus/client/log_subscriber.rb +64 -0
  18. data/lib/morpheus/client/railtie.rb +25 -0
  19. data/lib/morpheus/configuration.rb +49 -0
  20. data/lib/morpheus/errors.rb +32 -0
  21. data/lib/morpheus/filter.rb +18 -0
  22. data/lib/morpheus/mixins/associations.rb +55 -0
  23. data/lib/morpheus/mixins/attributes.rb +133 -0
  24. data/lib/morpheus/mixins/conversion.rb +21 -0
  25. data/lib/morpheus/mixins/filtering.rb +18 -0
  26. data/lib/morpheus/mixins/finders.rb +58 -0
  27. data/lib/morpheus/mixins/introspection.rb +25 -0
  28. data/lib/morpheus/mixins/persistence.rb +46 -0
  29. data/lib/morpheus/mixins/reflections.rb +24 -0
  30. data/lib/morpheus/mixins/request_handling.rb +34 -0
  31. data/lib/morpheus/mixins/response_parsing.rb +27 -0
  32. data/lib/morpheus/mixins/url_support.rb +36 -0
  33. data/lib/morpheus/mock.rb +66 -0
  34. data/lib/morpheus/reflection.rb +22 -0
  35. data/lib/morpheus/relation.rb +57 -0
  36. data/lib/morpheus/request.rb +41 -0
  37. data/lib/morpheus/request_cache.rb +18 -0
  38. data/lib/morpheus/request_queue.rb +44 -0
  39. data/lib/morpheus/response.rb +24 -0
  40. data/lib/morpheus/response_parser.rb +80 -0
  41. data/lib/morpheus/type_caster.rb +80 -0
  42. data/lib/morpheus/url_builder.rb +52 -0
  43. data/lib/morpheus.rb +64 -0
  44. data/morpheus.gemspec +191 -0
  45. data/spec/dummy/Rakefile +7 -0
  46. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  47. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  48. data/spec/dummy/app/models/purchase.rb +3 -0
  49. data/spec/dummy/app/resources/attendee.rb +2 -0
  50. data/spec/dummy/app/resources/author.rb +5 -0
  51. data/spec/dummy/app/resources/automobile.rb +6 -0
  52. data/spec/dummy/app/resources/book.rb +5 -0
  53. data/spec/dummy/app/resources/conference.rb +3 -0
  54. data/spec/dummy/app/resources/dog.rb +10 -0
  55. data/spec/dummy/app/resources/item.rb +5 -0
  56. data/spec/dummy/app/resources/meeting.rb +7 -0
  57. data/spec/dummy/app/resources/speaker.rb +3 -0
  58. data/spec/dummy/app/resources/state.rb +5 -0
  59. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  60. data/spec/dummy/config/application.rb +45 -0
  61. data/spec/dummy/config/boot.rb +10 -0
  62. data/spec/dummy/config/database.yml +22 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +26 -0
  65. data/spec/dummy/config/environments/production.rb +49 -0
  66. data/spec/dummy/config/environments/test.rb +35 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/inflections.rb +10 -0
  69. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  70. data/spec/dummy/config/initializers/morpheus.rb +3 -0
  71. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  72. data/spec/dummy/config/initializers/session_store.rb +8 -0
  73. data/spec/dummy/config/locales/en.yml +5 -0
  74. data/spec/dummy/config/routes.rb +58 -0
  75. data/spec/dummy/config.ru +4 -0
  76. data/spec/dummy/db/migrate/20110605002144_create_purchases.rb +13 -0
  77. data/spec/dummy/db/test.sqlite3 +0 -0
  78. data/spec/dummy/public/404.html +26 -0
  79. data/spec/dummy/public/422.html +26 -0
  80. data/spec/dummy/public/500.html +26 -0
  81. data/spec/dummy/public/favicon.ico +0 -0
  82. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  83. data/spec/dummy/script/rails +6 -0
  84. data/spec/morpheus/associations/association_spec.rb +44 -0
  85. data/spec/morpheus/associations/belongs_to_association_spec.rb +5 -0
  86. data/spec/morpheus/associations/has_many_association_spec.rb +17 -0
  87. data/spec/morpheus/associations/has_one_association_spec.rb +5 -0
  88. data/spec/morpheus/base_spec.rb +126 -0
  89. data/spec/morpheus/client/associations_spec.rb +44 -0
  90. data/spec/morpheus/configuration_spec.rb +136 -0
  91. data/spec/morpheus/mixins/associations_spec.rb +141 -0
  92. data/spec/morpheus/mixins/attributes_spec.rb +99 -0
  93. data/spec/morpheus/mixins/conversion_spec.rb +76 -0
  94. data/spec/morpheus/mixins/finders_spec.rb +255 -0
  95. data/spec/morpheus/mixins/introspection_spec.rb +154 -0
  96. data/spec/morpheus/mixins/persistence_spec.rb +161 -0
  97. data/spec/morpheus/mixins/reflection_spec.rb +100 -0
  98. data/spec/morpheus/mixins/response_parsing_spec.rb +5 -0
  99. data/spec/morpheus/mock_spec.rb +133 -0
  100. data/spec/morpheus/relation_spec.rb +71 -0
  101. data/spec/morpheus/request_cache_spec.rb +5 -0
  102. data/spec/morpheus/request_spec.rb +5 -0
  103. data/spec/morpheus/response_spec.rb +73 -0
  104. data/spec/morpheus/type_caster_spec.rb +343 -0
  105. data/spec/shared/active_model_lint_test.rb +14 -0
  106. data/spec/spec_helper.rb +32 -0
  107. data/spec/support/configuration.rb +26 -0
  108. metadata +427 -0
@@ -0,0 +1,47 @@
1
+ module Morpheus
2
+ module Client
3
+ module Associations
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ def belongs_to_resource(association, options = {})
9
+ define_method association do
10
+ instance_variable_get("@#{association}") ||
11
+ instance_variable_set("@#{association}", Morpheus::Associations::BelongsToAssociation.new(self, association, :options => options))
12
+ end
13
+
14
+ define_method "#{association}=" do |target|
15
+ instance_variable_set("@#{association}", Morpheus::Associations::BelongsToAssociation.new(self, association, :target => target, :options => options))
16
+ send("#{association}_id=", target.id)
17
+ end
18
+
19
+ end
20
+
21
+ def has_many_resources(association, options = {})
22
+ define_method association do
23
+ instance_variable_get("@#{association}") ||
24
+ instance_variable_set("@#{association}", Morpheus::Associations::HasManyAssociation.new(self, association, :options => options))
25
+ end
26
+
27
+ define_method "#{association}=" do |target|
28
+ instance_variable_set("@#{association}", Morpheus::Associations::HasManyAssociation.new(self, association, :target => target, :options => options))
29
+ end
30
+ end
31
+
32
+ def has_one_resource(association, options = {})
33
+ define_method association do
34
+ instance_variable_get("@#{association}") ||
35
+ instance_variable_set("@#{association}", Morpheus::Associations::HasOneAssociation.new(self, association, :options => options))
36
+ end
37
+
38
+ define_method "#{association}=" do |target|
39
+ instance_variable_set("@#{association}", Morpheus::Associations::HasOneAssociation.new(self, association, :target => target, :options => options))
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ ActiveSupport::Inflector.inflections do |inflect|
2
+ inflect.singular /^(.+)ss$/i, '\1ss'
3
+ end
@@ -0,0 +1,64 @@
1
+ module Morpheus
2
+ module Client
3
+ class LogSubscriber < ActiveSupport::LogSubscriber
4
+
5
+ def initialize
6
+ super
7
+ @odd_or_even = false
8
+ end
9
+
10
+ def request(event)
11
+ debug " #{event.payload[:response].cached? ? cached_request(event) : uncached_request(event)}"
12
+ end
13
+
14
+ def uncached_request(event)
15
+ duration = event.payload[:response].time * 1000
16
+ request_statement = "#{event.payload[:class].to_s} #{event.payload[:method].to_s.upcase} (#{duration}ms)"
17
+ if odd?
18
+ request_statement = color(request_statement, MAGENTA, true)
19
+ payload = color(payload(event), nil, true)
20
+ else
21
+ request_statement = color(request_statement, CYAN, true)
22
+ payload = payload(event)
23
+ end
24
+
25
+ response_color = case event.payload[:response].code
26
+ when 200..299 then GREEN
27
+ when 400..599 then RED
28
+ else YELLOW
29
+ end
30
+ response_statement = color("[#{event.payload[:response].code}]", response_color, true)
31
+
32
+ "#{request_statement} #{response_statement} #{payload}"
33
+ end
34
+
35
+ def cached_request(event)
36
+ duration = event.duration
37
+ request_statement = "CACHE (#{duration}ms)"
38
+ if odd?
39
+ request_statement = color(request_statement, MAGENTA, true)
40
+ payload = color(payload(event), nil, true)
41
+ else
42
+ request_statement = color(request_statement, CYAN, true)
43
+ payload = payload(event)
44
+ end
45
+ "#{request_statement} #{payload}"
46
+ end
47
+
48
+ def payload(event)
49
+ payload = "#{event.payload[:url]}"
50
+ payload << " #{event.payload[:params].inspect}" unless event.payload[:params].blank?
51
+ payload
52
+ end
53
+
54
+ def logger
55
+ Morpheus::Configuration.logger
56
+ end
57
+
58
+ def odd?
59
+ @odd_or_even = !@odd_or_even
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ module Morpheus
2
+ module Client
3
+ class Railtie < Rails::Railtie
4
+
5
+ if defined?(ActiveRecord)
6
+ ActiveRecord::Base.send(:include, Morpheus::Client::Associations)
7
+ ActiveRecord::Base.send(:include, Morpheus::UrlSupport)
8
+ end
9
+
10
+ initializer "morpheus.client.configure_request_cache" do |app|
11
+ app.middleware.use Morpheus::RequestQueue
12
+ app.middleware.use Morpheus::RequestCache
13
+ end
14
+
15
+ initializer "morpheus.configure_logger", :after => :initialize_logger do |app|
16
+ Morpheus::Configuration.logger = Rails.logger
17
+ end
18
+
19
+ initializer "morpheus.client.log_subscriber", :after => "morpheus.configure_logger" do
20
+ Morpheus::Client::LogSubscriber.attach_to :morpheus
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module Morpheus
2
+ class Configuration
3
+
4
+ class << self
5
+
6
+ attr_accessor :username, :password
7
+
8
+ # Sets and gets the remote host service domain.
9
+ # The domain must be set before any requests can be made.
10
+ attr_writer :host
11
+ def host
12
+ @host || raise(Morpheus::ConfigurationError, "The request HOST has not been set. Please set the host using Morpheus::Configuration.host = 'http://www.example.com'")
13
+ end
14
+
15
+ # Links to the underlying libcurl request pool.
16
+ # Allows for concurrent requests.
17
+ def hydra
18
+ Typhoeus::Hydra.hydra
19
+ end
20
+
21
+ def hydra=(hydra)
22
+ Typhoeus::Hydra.hydra = hydra
23
+ end
24
+
25
+ # Handles the default logger that is used by the LogSubscriber
26
+ def logger
27
+ @logger ||= ::Logger.new(STDOUT)
28
+ end
29
+
30
+ def logger=(logger)
31
+ @logger = logger
32
+ end
33
+
34
+ # Can be set or unset to allow for test suite mocking.
35
+ def allow_net_connect=(allowed)
36
+ Typhoeus::Hydra.allow_net_connect = allowed
37
+ end
38
+
39
+ # Allow net connections by default.
40
+ allow_net_connect = true
41
+
42
+ def allow_net_connect?
43
+ Typhoeus::Hydra.allow_net_connect?
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ module Morpheus
2
+ # Occurs when a configuration issue occurs, like when the remote host is not set.
3
+ class ConfigurationError < ::StandardError
4
+ end
5
+
6
+ # Occurs when a request from a remote host is made while Morpheus::Configuration.allow_net_connect is set to false.
7
+ class NetConnectNotAllowedError < ::StandardError
8
+ attr_accessor :request
9
+ def initialize(_request=nil)
10
+ self.request= _request
11
+ end
12
+ end
13
+
14
+ # Occurs when the remote host is unreachable.
15
+ class RemoteHostConnectionFailure < ::StandardError
16
+ end
17
+
18
+ # Occurs when there is a server error resulting in a 500 response code
19
+ class ServerError < ::StandardError
20
+ end
21
+
22
+ # Occurs when the requested resource returns a 404 error, indicating that it could not be found.
23
+ class ResourceNotFound < ::StandardError
24
+ end
25
+
26
+ # Occurs when the typecast type is not one of the known values.
27
+ class UnrecognizedTypeCastClass < ::StandardError
28
+ end
29
+
30
+ class InvalidResponseCode < ::StandardError
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module Morpheus
2
+ class Filter
3
+
4
+ def initialize(name, &block)
5
+ @name = name
6
+ @block = block
7
+ end
8
+
9
+ def name
10
+ @name
11
+ end
12
+
13
+ def call(element)
14
+ @block.call(element)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,55 @@
1
+ module Morpheus
2
+ module Associations
3
+ autoload :Association, 'morpheus/associations/association'
4
+ autoload :BelongsToAssociation, 'morpheus/associations/belongs_to_association'
5
+ autoload :HasManyAssociation, 'morpheus/associations/has_many_association'
6
+ autoload :HasOneAssociation, 'morpheus/associations/has_one_association'
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+
12
+ def belongs_to(association, options = {})
13
+ attribute_id_sym = "#{association}_id".to_sym
14
+
15
+ property attribute_id_sym
16
+
17
+ create_reflection(:belongs_to, association, options)
18
+
19
+ define_method association do
20
+ instance_variable_get("@#{association}") ||
21
+ instance_variable_set("@#{association}", BelongsToAssociation.new(self, association, :options => options))
22
+ end
23
+
24
+ define_method "#{association}=" do |target|
25
+ instance_variable_set("@#{association}", BelongsToAssociation.new(self, association, :target => target, :options => options))
26
+ send("#{association}_id=", target.id)
27
+ end
28
+ end
29
+
30
+ def has_many(association, options = {})
31
+ create_reflection(:has_many, association, options)
32
+
33
+ define_method association do
34
+ instance_variable_get("@#{association}") ||
35
+ instance_variable_set("@#{association}", HasManyAssociation.new(self, association, :options => options))
36
+ end
37
+ end
38
+
39
+ def has_one(association, options = {})
40
+ create_reflection(:has_one, association, options)
41
+
42
+ define_method association do
43
+ instance_variable_get("@#{association}") ||
44
+ instance_variable_set("@#{association}", HasOneAssociation.new(self, association, :options => options))
45
+ end
46
+
47
+ define_method "#{association}=" do |target|
48
+ instance_variable_set("@#{association}", HasOneAssociation.new(self, association, :target => target, :options => options))
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,133 @@
1
+ module Morpheus
2
+ module Attributes
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def property(name, typecast_class = nil)
8
+ typecast_attributes.store(name.to_sym, typecast_class)
9
+ default_attributes.store(name.to_sym, nil)
10
+
11
+ define_attribute_accessor(name)
12
+ end
13
+
14
+ def default_attributes
15
+ @attributes ||= HashWithIndifferentAccess.new(superclass.respond_to?(:default_attributes) ? superclass.default_attributes : {})
16
+ end
17
+
18
+ def typecast_attributes
19
+ @typecast_attributes ||= HashWithIndifferentAccess.new(superclass.respond_to?(:default_attributes) ? superclass.default_attributes : {})
20
+ end
21
+
22
+ def attribute_defined?(attribute)
23
+ default_attributes.keys.include?(attribute.to_s)
24
+ end
25
+
26
+ def define_attribute_accessor(method)
27
+ method = method.to_s.delete('=')
28
+ define_method("#{method}=") do |value|
29
+ send("#{method}_will_change!")
30
+ update_attribute(method, value)
31
+ end
32
+
33
+ define_method(method) do
34
+ attributes[method]
35
+ end
36
+
37
+ define_attribute_methods [method]
38
+ end
39
+ private :define_attribute_accessor
40
+
41
+ end
42
+
43
+ module InstanceMethods
44
+
45
+ def attributes
46
+ @attributes ||= self.class.default_attributes.dup
47
+ end
48
+
49
+ def read_attribute_for_validation(key)
50
+ attributes[key]
51
+ end
52
+
53
+ def typecast(attribute, value)
54
+ TypeCaster.cast(value, self.class.typecast_attributes[attribute.to_sym])
55
+ end
56
+
57
+ def attributes_without_basic_attributes
58
+ attributes_to_reject = %w( id errors valid )
59
+ self.class.reflections.keys.each do |key|
60
+ attributes_to_reject.push(key.to_s)
61
+ end
62
+ attributes.reject do |key, value|
63
+ attributes_to_reject.include?(key)
64
+ end
65
+ end
66
+
67
+ def update_attribute(attribute, value)
68
+ reflection = self.class.reflect_on_association(attribute)
69
+ if reflection
70
+ update_reflection(reflection, attribute, value)
71
+ else
72
+ attributes[attribute.to_sym] = typecast(attribute, value)
73
+ end
74
+ end
75
+
76
+ def update_reflection(reflection, attribute, value)
77
+ return unless value
78
+ if reflection.macro == :has_many # need to construct each member of array one-by-one
79
+ association_object = send(attribute)
80
+ value.each do |a_value|
81
+ if a_value.instance_of? reflection.klass
82
+ target = a_value
83
+ else
84
+ target = reflection.build_association(a_value)
85
+ end
86
+ association_object << target
87
+ end
88
+ elsif reflection.macro == :belongs_to
89
+ if value.instance_of? reflection.klass
90
+ target = value
91
+ else
92
+ if reflection.options[:polymorphic]
93
+ polymorphic_class = send("#{reflection.name}_type".to_sym)
94
+ polymorphic_class = value['type'] if value.include?('type')
95
+ target = polymorphic_class.constantize.new(value)
96
+ else
97
+ target = reflection.build_association(value)
98
+ end
99
+ end
100
+ send("#{attribute}=", target)
101
+ else
102
+ if value.instance_of? reflection.klass
103
+ target = value
104
+ else
105
+ target = reflection.build_association(value)
106
+ end
107
+ send("#{attribute}=", target)
108
+ end
109
+ end
110
+
111
+ def merge_attributes(new_attributes)
112
+ new_attributes.each do |key, value|
113
+ case key.to_sym
114
+ when :errors
115
+ value.each do |k, v|
116
+ v.each do |message|
117
+ errors.add(k, message)
118
+ end
119
+ end
120
+ when :valid
121
+ @valid = value
122
+ else
123
+ update_attribute(key, value)
124
+ end
125
+ end
126
+ self
127
+ end
128
+ private :merge_attributes
129
+
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,21 @@
1
+ module Morpheus
2
+ module Conversion
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+
7
+ def to_model
8
+ self
9
+ end
10
+
11
+ def to_param
12
+ id.to_s unless new_record?
13
+ end
14
+
15
+ def to_key
16
+ attributes.keys if persisted?
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module Morpheus
2
+ module Filtering
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def filter(name, &block)
8
+ (@filters ||= []) << Filter.new(name, &block)
9
+ end
10
+
11
+ def find_filter(name)
12
+ (@filters ||= []).find { |filter| filter.name == name }
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,58 @@
1
+ module Morpheus
2
+ module Finders
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def find(*args)
8
+ options = args.extract_options!
9
+ if args.length == 1
10
+ case args.first
11
+ when Integer, String
12
+ attributes = [UrlBuilder.find_one(self, args.first.to_i), nil, { :id => args.first.to_i }]
13
+ get(*attributes)
14
+ when Array
15
+ attributes = UrlBuilder.find_some(self, args.first.sort).push({ :ids => args.first })
16
+ get(*attributes)
17
+ else
18
+ raise ArgumentError, "Unrecognized argument (#{args.first.inspect})."
19
+ end
20
+ else
21
+ attributes = UrlBuilder.find_some(self, args.sort).push({ :ids => args })
22
+ get(*attributes)
23
+ end
24
+ end
25
+
26
+ def all
27
+ attributes = [UrlBuilder.find_all(self), nil, {}]
28
+ get(*attributes)
29
+ end
30
+
31
+ def first
32
+ limit(1).first
33
+ end
34
+
35
+ def where(query_attributes)
36
+ Relation.new(self).where(query_attributes)
37
+ end
38
+
39
+ def limit(amount)
40
+ Relation.new(self).limit(amount)
41
+ end
42
+
43
+ def page(page_number)
44
+ Relation.new(self).page(page_number)
45
+ end
46
+
47
+ def results_per_page
48
+ @results_per_page || 10
49
+ end
50
+
51
+ def results_per_page=(_results_per_page)
52
+ @results_per_page = _results_per_page
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ module Morpheus
2
+ module Introspection
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+
7
+ def persisted?
8
+ !id.nil?
9
+ end
10
+
11
+ def new_record?
12
+ !persisted?
13
+ end
14
+
15
+ def destroyed?
16
+ false
17
+ end
18
+
19
+ def respond_to?(method, include_private = false)
20
+ attributes.include?(method) ? true : super
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ module Morpheus
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+
7
+ def save(with_validations = true)
8
+ attributes_for_save = { self.class.attributes_root => attributes_without_basic_attributes.reject { |k,v| v.nil? } }
9
+
10
+ if with_validations
11
+ return false unless valid?
12
+ end
13
+
14
+ if new_record?
15
+ built_object = self.class.post(*UrlBuilder.save(self.class, nil, attributes_for_save))
16
+ else
17
+ built_object = self.class.put(*UrlBuilder.save(self.class, id, attributes_for_save))
18
+ end
19
+ built_object.instance_variables.each do |iv|
20
+ self.instance_variable_set(iv, built_object.instance_variable_get(iv))
21
+ end
22
+ @valid
23
+ end
24
+
25
+ def update_attributes(new_attributes)
26
+ merge_attributes(new_attributes)
27
+ save
28
+ end
29
+
30
+ def destroy
31
+ self.class.delete(UrlBuilder.destroy(self.class, id))
32
+ end
33
+
34
+ end
35
+
36
+ module ClassMethods
37
+
38
+ def create(new_attributes = {})
39
+ new(new_attributes).tap { |new_instance| new_instance.save }
40
+ end
41
+ alias :create! :create
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ module Morpheus
2
+ module Reflections
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def create_reflection(macro, name, options)
8
+ Reflection.new(macro, name, options).tap do |reflection|
9
+ reflections.merge!(name => reflection)
10
+ end
11
+ end
12
+
13
+ def reflections
14
+ @reflections ||= {}
15
+ end
16
+
17
+ def reflect_on_association(association)
18
+ reflections[association.to_sym].is_a?(Reflection) ? reflections[association.to_sym] : nil
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ module Morpheus
2
+ module RequestHandling
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def get(path, params = nil, metadata = {}, &block)
8
+ request = Request.enqueue(:get, path, params)
9
+ request.on_complete = block if block_given?
10
+ response_from_request(request, metadata)
11
+ end
12
+
13
+ def post(path, params = nil, metadata = {}, &block)
14
+ request = Request.enqueue(:post, path, params)
15
+ request.on_complete = block if block_given?
16
+ response_from_request(request, metadata)
17
+ end
18
+
19
+ def put(path, params = nil, metadata = {}, &block)
20
+ request = Request.enqueue(:put, path, params)
21
+ request.on_complete = block if block_given?
22
+ response_from_request(request, metadata)
23
+ end
24
+
25
+ def delete(path, params = nil, metadata = {}, &block)
26
+ request = Request.enqueue(:delete, path, params)
27
+ request.on_complete = block if block_given?
28
+ response_from_request(request, metadata)
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ module Morpheus
2
+ module ResponseParsing
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def response_from_request(request, metadata)
8
+ ResponseParser.parse(self, request, metadata)
9
+ end
10
+
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ def build_from_response(response)
16
+ content = Yajl::Parser.parse(response.body)['content']
17
+ if content.keys.include?('type')
18
+ content['type'].constantize.new.merge_attributes(content)
19
+ else
20
+ merge_attributes(content)
21
+ end
22
+ end
23
+ private :build_from_response
24
+
25
+ end
26
+ end
27
+ end