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,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,18 @@
1
+ module Morpheus
2
+ class RequestCache
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ self.class.cache.clear
10
+ @app.call(env)
11
+ end
12
+
13
+ def self.cache
14
+ @cache ||= {}
15
+ end
16
+
17
+ end
18
+ 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'