morpheus 0.3.4

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