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