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.
- data/.rvmrc +1 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +134 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +44 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +7 -0
- data/lib/ext/typhoeus.rb +37 -0
- data/lib/morpheus/associations/association.rb +110 -0
- data/lib/morpheus/associations/belongs_to_association.rb +45 -0
- data/lib/morpheus/associations/has_many_association.rb +70 -0
- data/lib/morpheus/associations/has_one_association.rb +46 -0
- data/lib/morpheus/base.rb +66 -0
- data/lib/morpheus/client/associations.rb +47 -0
- data/lib/morpheus/client/inflections.rb +3 -0
- data/lib/morpheus/client/log_subscriber.rb +64 -0
- data/lib/morpheus/client/railtie.rb +25 -0
- data/lib/morpheus/configuration.rb +49 -0
- data/lib/morpheus/errors.rb +32 -0
- data/lib/morpheus/filter.rb +18 -0
- data/lib/morpheus/mixins/associations.rb +55 -0
- data/lib/morpheus/mixins/attributes.rb +133 -0
- data/lib/morpheus/mixins/conversion.rb +21 -0
- data/lib/morpheus/mixins/filtering.rb +18 -0
- data/lib/morpheus/mixins/finders.rb +58 -0
- data/lib/morpheus/mixins/introspection.rb +25 -0
- data/lib/morpheus/mixins/persistence.rb +46 -0
- data/lib/morpheus/mixins/reflections.rb +24 -0
- data/lib/morpheus/mixins/request_handling.rb +34 -0
- data/lib/morpheus/mixins/response_parsing.rb +27 -0
- data/lib/morpheus/mixins/url_support.rb +36 -0
- data/lib/morpheus/mock.rb +66 -0
- data/lib/morpheus/reflection.rb +22 -0
- data/lib/morpheus/relation.rb +57 -0
- data/lib/morpheus/request.rb +41 -0
- data/lib/morpheus/request_cache.rb +18 -0
- data/lib/morpheus/request_queue.rb +44 -0
- data/lib/morpheus/response.rb +24 -0
- data/lib/morpheus/response_parser.rb +80 -0
- data/lib/morpheus/type_caster.rb +80 -0
- data/lib/morpheus/url_builder.rb +52 -0
- data/lib/morpheus.rb +64 -0
- data/morpheus.gemspec +191 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/purchase.rb +3 -0
- data/spec/dummy/app/resources/attendee.rb +2 -0
- data/spec/dummy/app/resources/author.rb +5 -0
- data/spec/dummy/app/resources/automobile.rb +6 -0
- data/spec/dummy/app/resources/book.rb +5 -0
- data/spec/dummy/app/resources/conference.rb +3 -0
- data/spec/dummy/app/resources/dog.rb +10 -0
- data/spec/dummy/app/resources/item.rb +5 -0
- data/spec/dummy/app/resources/meeting.rb +7 -0
- data/spec/dummy/app/resources/speaker.rb +3 -0
- data/spec/dummy/app/resources/state.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/morpheus.rb +3 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20110605002144_create_purchases.rb +13 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/morpheus/associations/association_spec.rb +44 -0
- data/spec/morpheus/associations/belongs_to_association_spec.rb +5 -0
- data/spec/morpheus/associations/has_many_association_spec.rb +17 -0
- data/spec/morpheus/associations/has_one_association_spec.rb +5 -0
- data/spec/morpheus/base_spec.rb +126 -0
- data/spec/morpheus/client/associations_spec.rb +44 -0
- data/spec/morpheus/configuration_spec.rb +136 -0
- data/spec/morpheus/mixins/associations_spec.rb +141 -0
- data/spec/morpheus/mixins/attributes_spec.rb +99 -0
- data/spec/morpheus/mixins/conversion_spec.rb +76 -0
- data/spec/morpheus/mixins/finders_spec.rb +255 -0
- data/spec/morpheus/mixins/introspection_spec.rb +154 -0
- data/spec/morpheus/mixins/persistence_spec.rb +161 -0
- data/spec/morpheus/mixins/reflection_spec.rb +100 -0
- data/spec/morpheus/mixins/response_parsing_spec.rb +5 -0
- data/spec/morpheus/mock_spec.rb +133 -0
- data/spec/morpheus/relation_spec.rb +71 -0
- data/spec/morpheus/request_cache_spec.rb +5 -0
- data/spec/morpheus/request_spec.rb +5 -0
- data/spec/morpheus/response_spec.rb +73 -0
- data/spec/morpheus/type_caster_spec.rb +343 -0
- data/spec/shared/active_model_lint_test.rb +14 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/configuration.rb +26 -0
- 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,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,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
|