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,36 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
module UrlSupport
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
module ClassMethods
|
|
6
|
+
|
|
7
|
+
def url_name
|
|
8
|
+
@url_name ||= model_name.underscore
|
|
9
|
+
end
|
|
10
|
+
alias_method :singular_url_name, :url_name
|
|
11
|
+
|
|
12
|
+
def plural_url_name
|
|
13
|
+
url_name.pluralize
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def set_base_url(url_name)
|
|
17
|
+
@url_name = url_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def attributes_root
|
|
21
|
+
@attributes_root ||= model_name.underscore
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set_attributes_root(name)
|
|
25
|
+
@attributes_root = name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set_base_model_name(name)
|
|
29
|
+
set_base_url(name)
|
|
30
|
+
set_attributes_root(name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# The Mock class is used to mock out single instances of a remote resource.
|
|
2
|
+
# It will stub any requests to /:class_name/:id.
|
|
3
|
+
module Morpheus
|
|
4
|
+
class Mock
|
|
5
|
+
|
|
6
|
+
def initialize(mock_class, id, attributes = {}, options = {})
|
|
7
|
+
@mock_class = mock_class.to_s
|
|
8
|
+
@attributes = HashWithIndifferentAccess.new(attributes).merge({ 'id' => id })
|
|
9
|
+
@options = options
|
|
10
|
+
|
|
11
|
+
@attributes.keys.each do |attribute|
|
|
12
|
+
(class << self; self; end).class_eval do
|
|
13
|
+
define_method attribute do
|
|
14
|
+
@attributes[attribute]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
define_method "#{attribute}=" do |value|
|
|
18
|
+
@attributes[attribute] = value
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
stub_finder_methods!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def id
|
|
27
|
+
@attributes[:id]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def status
|
|
31
|
+
@options[:status] || 200
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ==(other_object)
|
|
35
|
+
HashWithIndifferentAccess.new(other_object.attributes) == @attributes && other_object.class.to_s == @mock_class
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.mock(mock_class, id, attributes = {}, options = {})
|
|
39
|
+
new(mock_class, id, attributes, options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.clear!
|
|
43
|
+
Configuration.hydra.clear_stubs
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def stub_finder_methods!
|
|
49
|
+
stub_find_one!
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def stub_find_one!
|
|
53
|
+
response = Morpheus::Response.new(:code => status, :headers => "", :body => stub_find_one_body, :time => 0.3)
|
|
54
|
+
Configuration.hydra.stub(:get, find_one_uri).and_return(response)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def stub_find_one_body
|
|
58
|
+
{ :status => status, :content => @attributes.merge({ :id => 1 }) }.to_json
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def find_one_uri
|
|
62
|
+
"#{Configuration.host}/#{@mock_class.underscore.pluralize}/#{id}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class Reflection
|
|
3
|
+
attr_reader :macro, :name, :options
|
|
4
|
+
|
|
5
|
+
def initialize(macro, name, options = {})
|
|
6
|
+
@macro, @name, @options = macro, name, options
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def klass
|
|
10
|
+
@klass ||= class_name.constantize
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def class_name
|
|
14
|
+
(options[:class_name] || name).to_s.singularize.camelize
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_association(*options)
|
|
18
|
+
klass.new(*options)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class Relation < ActiveSupport::BasicObject
|
|
3
|
+
|
|
4
|
+
def initialize(owner)
|
|
5
|
+
@owner = owner
|
|
6
|
+
@target = nil
|
|
7
|
+
@params = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def where(attributes)
|
|
11
|
+
@params.merge!(attributes)
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def limit(amount)
|
|
16
|
+
@params.merge!(:limit => amount)
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def page(page_number)
|
|
21
|
+
offset = (page_number - 1) * @owner.results_per_page
|
|
22
|
+
@params.merge!(:limit => @owner.results_per_page, :offset => offset)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def all
|
|
27
|
+
loaded_target
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_a
|
|
31
|
+
loaded_target
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_json(*args)
|
|
35
|
+
to_a.to_json(*args)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def loaded_target
|
|
41
|
+
@target ||= load_target!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def load_target!
|
|
45
|
+
@owner.get(*UrlBuilder.relation(@owner, @params))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def method_missing(m, *args, &block)
|
|
49
|
+
if Array.method_defined?(m)
|
|
50
|
+
loaded_target.send(m, *args, &block)
|
|
51
|
+
else
|
|
52
|
+
loaded_target.send(m, *args, &block)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class Request < Typhoeus::Request
|
|
3
|
+
attr_reader :path, :params, :method
|
|
4
|
+
|
|
5
|
+
def initialize(path, options = {})
|
|
6
|
+
if options[:method] == :put
|
|
7
|
+
options[:method] = :post
|
|
8
|
+
options[:params].merge!(:_method => :put)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
options[:username] = Configuration.username if Configuration.username
|
|
12
|
+
options[:password] = Configuration.password if Configuration.password
|
|
13
|
+
|
|
14
|
+
super(Configuration.host + path, options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def cache_key
|
|
18
|
+
[method, url, params].hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def response=(response)
|
|
22
|
+
RequestCache.cache[cache_key] = response
|
|
23
|
+
response.tag_for_caching!
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def response
|
|
28
|
+
RequestQueue.run! if RequestQueue.has_request?(self)
|
|
29
|
+
RequestCache.cache[cache_key] || super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.enqueue(method, path, params)
|
|
33
|
+
options = { :method => method }
|
|
34
|
+
options.merge!(:params => params) if params
|
|
35
|
+
new(path, options).tap do |request|
|
|
36
|
+
RequestQueue.enqueue(request)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class RequestQueue
|
|
3
|
+
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
self.class.queue.clear
|
|
10
|
+
@app.call(env)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
|
|
15
|
+
def enqueue(request)
|
|
16
|
+
queue << request
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def queue
|
|
20
|
+
@queue ||= []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def uncached_queue
|
|
24
|
+
queue.collect { |request| request if RequestCache.cache[request.cache_key].nil? }.compact
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def hydra
|
|
28
|
+
Configuration.hydra
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run!
|
|
32
|
+
uncached_queue.each { |request| hydra.queue(request) }
|
|
33
|
+
hydra.run
|
|
34
|
+
queue.clear
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def has_request?(request)
|
|
38
|
+
queue.include?(request)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class Response < Typhoeus::Response
|
|
3
|
+
|
|
4
|
+
def initialize(params = {}, cached = false)
|
|
5
|
+
super(params)
|
|
6
|
+
@cached = cached
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def cached?
|
|
10
|
+
!!@cached.tap do
|
|
11
|
+
@cached = tagged_for_caching?
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def tag_for_caching!
|
|
16
|
+
@tagged_for_caching = true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tagged_for_caching?
|
|
20
|
+
@tagged_for_caching ||= false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class ResponseParser
|
|
3
|
+
|
|
4
|
+
def initialize(owner, request, metadata)
|
|
5
|
+
@requesting_class = owner
|
|
6
|
+
@request = request
|
|
7
|
+
@response = request.response
|
|
8
|
+
@metadata = metadata
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.parse(owner, request, metadata)
|
|
12
|
+
parser = new(owner, request, metadata)
|
|
13
|
+
|
|
14
|
+
ActiveSupport::Notifications.instrument('request.morpheus', :url => request.url, :params => request.params, :method => request.method, :class => owner, :response => request.response) do
|
|
15
|
+
@parsed_response = parser.parse
|
|
16
|
+
end
|
|
17
|
+
RequestCache.cache.clear unless request.method == :get
|
|
18
|
+
@parsed_response
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parse
|
|
22
|
+
case @response.code
|
|
23
|
+
when 200..299
|
|
24
|
+
build_from_response
|
|
25
|
+
when 404
|
|
26
|
+
raise_not_found
|
|
27
|
+
when 422
|
|
28
|
+
build_from_response
|
|
29
|
+
when 500..599
|
|
30
|
+
raise ServerError
|
|
31
|
+
when 0
|
|
32
|
+
raise RemoteHostConnectionFailure
|
|
33
|
+
else
|
|
34
|
+
raise InvalidResponseCode, "Response had error code: #{@response.code}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def content
|
|
41
|
+
@content ||= Yajl::Parser.parse(@response.body).try(:[], 'content')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def response_errors
|
|
45
|
+
@response_errors ||= Yajl::Parser.parse(@response.body).try(:[], 'errors')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_from_response
|
|
49
|
+
if content
|
|
50
|
+
if Array === content
|
|
51
|
+
content.collect do |attributes|
|
|
52
|
+
obj = build_object_with_attributes(attributes)
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
obj = build_object_with_attributes(content)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_object_with_attributes(attributes)
|
|
61
|
+
if attributes.keys.include?('type')
|
|
62
|
+
attributes['type'].constantize.new(attributes)
|
|
63
|
+
else
|
|
64
|
+
@requesting_class.new.send(:merge_attributes, attributes)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def raise_not_found
|
|
69
|
+
case
|
|
70
|
+
when @metadata[:ids]
|
|
71
|
+
raise ResourceNotFound, "Couldn't find all #{@requesting_class.to_s.pluralize} with IDs (#{@metadata[:ids].join(', ')})"
|
|
72
|
+
when @metadata[:id]
|
|
73
|
+
raise ResourceNotFound, "Couldn't find #{@requesting_class} with ID=#{@metadata[:id]}"
|
|
74
|
+
else
|
|
75
|
+
raise ResourceNotFound, "No resource was found at #{@request.url} #{response_errors.inspect if response_errors}"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class TypeCaster
|
|
3
|
+
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
def cast(value, typecast_class)
|
|
7
|
+
case typecast_class
|
|
8
|
+
when NilClass
|
|
9
|
+
value
|
|
10
|
+
when :string
|
|
11
|
+
parse_string(value)
|
|
12
|
+
when :integer
|
|
13
|
+
parse_integer(value)
|
|
14
|
+
when :datetime
|
|
15
|
+
parse_datetime(value)
|
|
16
|
+
when :date
|
|
17
|
+
parse_date(value)
|
|
18
|
+
when :time
|
|
19
|
+
parse_time(value)
|
|
20
|
+
when :boolean
|
|
21
|
+
parse_boolean(value)
|
|
22
|
+
else
|
|
23
|
+
raise UnrecognizedTypeCastClass, "Can't typecast to #{typecast_class}!"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def parse_string(value)
|
|
30
|
+
value.to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parse_integer(value)
|
|
34
|
+
if value.respond_to?(:to_i)
|
|
35
|
+
value.to_i
|
|
36
|
+
else
|
|
37
|
+
case value
|
|
38
|
+
when TrueClass then 1
|
|
39
|
+
when FalseClass then 0
|
|
40
|
+
when Date then value.to_time.utc.to_i
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_datetime(value)
|
|
46
|
+
if value.respond_to?(:to_datetime)
|
|
47
|
+
value.to_datetime
|
|
48
|
+
else
|
|
49
|
+
DateTime.parse(value) unless [TrueClass, FalseClass, NilClass, Fixnum].include?(value.class)
|
|
50
|
+
end
|
|
51
|
+
rescue ArgumentError
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parse_date(value)
|
|
56
|
+
if value.respond_to?(:to_time)
|
|
57
|
+
parse_time(value).try(:to_date)
|
|
58
|
+
else
|
|
59
|
+
Date.parse(value) unless [TrueClass, FalseClass, NilClass, Fixnum].include?(value.class)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parse_time(value)
|
|
64
|
+
if value.respond_to?(:to_time)
|
|
65
|
+
value.to_time
|
|
66
|
+
else
|
|
67
|
+
Time.parse(value) unless [TrueClass, FalseClass, NilClass, Fixnum].include?(value.class)
|
|
68
|
+
end
|
|
69
|
+
rescue ArgumentError
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_boolean(value)
|
|
74
|
+
!["nil", nil, "false", false, "0", 0].include?(value)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Morpheus
|
|
2
|
+
class UrlBuilder
|
|
3
|
+
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
def find_one(klass, id)
|
|
7
|
+
"/#{klass.plural_url_name}/#{id}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find_some(klass, ids)
|
|
11
|
+
["/#{klass.plural_url_name}", { :ids => ids }]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def find_all(klass)
|
|
15
|
+
"/#{klass.plural_url_name}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def save(klass, id, parameters)
|
|
19
|
+
url_base = "/#{klass.plural_url_name}"
|
|
20
|
+
url_base << "/#{id}" if id
|
|
21
|
+
[url_base, parameters]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def belongs_to(klass, id)
|
|
25
|
+
"/#{klass.plural_url_name}/#{id}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_one(primary_class, primary_key, foreign_class)
|
|
29
|
+
"/#{primary_class.plural_url_name}/#{primary_key}/#{foreign_class.singular_url_name}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def has_many(primary_class, primary_key, foreign_class)
|
|
33
|
+
"/#{primary_class.plural_url_name}/#{primary_key}/#{foreign_class.plural_url_name}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def relation(klass, parameters = {})
|
|
37
|
+
url_base = "/#{klass.plural_url_name}"
|
|
38
|
+
if parameters.empty?
|
|
39
|
+
url_base
|
|
40
|
+
else
|
|
41
|
+
[url_base, parameters]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def destroy(klass, id)
|
|
46
|
+
"/#{klass.plural_url_name}/#{id}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/morpheus.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# We are using Yalj::Ruby for its JSON parsing hotness.
|
|
2
|
+
require 'yajl/json_gem'
|
|
3
|
+
|
|
4
|
+
# Typhoeus is used for HTTP requests. The libcurl library allows us to make multiple requests in parallel.
|
|
5
|
+
require 'typhoeus'
|
|
6
|
+
|
|
7
|
+
# ActiveModel provides validations, errors, naming...
|
|
8
|
+
require 'active_model'
|
|
9
|
+
|
|
10
|
+
# The bulk of the library consists of the Base class and its mix-in modules, the request/response wrappers,
|
|
11
|
+
# mocking, configuration, errors, and classes managing associations, reflections, relations, and typecasting.
|
|
12
|
+
module Morpheus
|
|
13
|
+
autoload :Base, 'morpheus/base'
|
|
14
|
+
autoload :Configuration, 'morpheus/configuration'
|
|
15
|
+
autoload :Filter, 'morpheus/filter'
|
|
16
|
+
autoload :Mock, 'morpheus/mock'
|
|
17
|
+
autoload :Reflection, 'morpheus/reflection'
|
|
18
|
+
autoload :Relation, 'morpheus/relation'
|
|
19
|
+
autoload :Request, 'morpheus/request'
|
|
20
|
+
autoload :RequestQueue, 'morpheus/request_queue'
|
|
21
|
+
autoload :RequestCache, 'morpheus/request_cache'
|
|
22
|
+
autoload :Response, 'morpheus/response'
|
|
23
|
+
autoload :ResponseParser, 'morpheus/response_parser'
|
|
24
|
+
autoload :TypeCaster, 'morpheus/type_caster'
|
|
25
|
+
autoload :UrlBuilder, 'morpheus/url_builder'
|
|
26
|
+
|
|
27
|
+
# Mixins provide behaviors to the Base class.
|
|
28
|
+
autoload :Associations, 'morpheus/mixins/associations'
|
|
29
|
+
autoload :Attributes, 'morpheus/mixins/attributes'
|
|
30
|
+
autoload :Conversion, 'morpheus/mixins/conversion'
|
|
31
|
+
autoload :Filtering, 'morpheus/mixins/filtering'
|
|
32
|
+
autoload :Finders, 'morpheus/mixins/finders'
|
|
33
|
+
autoload :Introspection, 'morpheus/mixins/introspection'
|
|
34
|
+
autoload :Persistence, 'morpheus/mixins/persistence'
|
|
35
|
+
autoload :Reflections, 'morpheus/mixins/reflections'
|
|
36
|
+
autoload :RequestHandling, 'morpheus/mixins/request_handling'
|
|
37
|
+
autoload :ResponseParsing, 'morpheus/mixins/response_parsing'
|
|
38
|
+
autoload :UrlSupport, 'morpheus/mixins/url_support'
|
|
39
|
+
|
|
40
|
+
# Error classes
|
|
41
|
+
autoload :ConfigurationError, 'morpheus/errors'
|
|
42
|
+
autoload :NetConnectNotAllowedError, 'morpheus/errors'
|
|
43
|
+
autoload :RemoteHostConnectionFailure, 'morpheus/errors'
|
|
44
|
+
autoload :ResourceNotFound, 'morpheus/errors'
|
|
45
|
+
autoload :UnrecognizedTypeCastClass, 'morpheus/errors'
|
|
46
|
+
autoload :ServerError, 'morpheus/errors'
|
|
47
|
+
autoload :InvalidResponseCode, 'morpheus/errors'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Associations for application objects, typically ActiveRecord, to resource
|
|
51
|
+
module Morpheus
|
|
52
|
+
module Client
|
|
53
|
+
autoload :Associations, 'morpheus/client/associations'
|
|
54
|
+
autoload :LogSubscriber, 'morpheus/client/log_subscriber'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The Railtie loads the LogSubscriber for printing output to the Rails log, and
|
|
59
|
+
# Associations which plug-in to ActiveRecord to link associated resources.
|
|
60
|
+
require 'morpheus/client/railtie' if defined?(Rails)
|
|
61
|
+
require 'morpheus/client/inflections'
|
|
62
|
+
|
|
63
|
+
# There are some Typhoeus patches contained here.
|
|
64
|
+
require 'ext/typhoeus'
|