morpheus 0.3.6 → 0.3.7

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.
Files changed (71) hide show
  1. data/.autotest +12 -0
  2. data/.rspec +1 -0
  3. data/README.md +77 -0
  4. data/Rakefile +16 -0
  5. data/lib/morpheus.rb +2 -2
  6. data/lib/morpheus/base.rb +1 -1
  7. data/lib/morpheus/client/inflections.rb +1 -1
  8. data/lib/morpheus/configuration.rb +41 -43
  9. data/lib/morpheus/errors.rb +1 -1
  10. data/lib/morpheus/mixins/associations.rb +3 -1
  11. data/lib/morpheus/mixins/attributes.rb +66 -67
  12. data/lib/morpheus/mixins/conversion.rb +9 -13
  13. data/lib/morpheus/mixins/filtering.rb +4 -1
  14. data/lib/morpheus/mixins/finders.rb +4 -1
  15. data/lib/morpheus/mixins/introspection.rb +12 -16
  16. data/lib/morpheus/mixins/persistence.rb +25 -26
  17. data/lib/morpheus/mixins/reflections.rb +4 -1
  18. data/lib/morpheus/mixins/request_handling.rb +4 -1
  19. data/lib/morpheus/mixins/response_parsing.rb +12 -12
  20. data/lib/morpheus/mixins/url_support.rb +4 -1
  21. data/lib/morpheus/request.rb +34 -34
  22. data/lib/morpheus/response.rb +2 -2
  23. data/lib/morpheus/response_parser.rb +9 -4
  24. data/lib/morpheus/version.rb +1 -1
  25. data/morpheus.gemspec +4 -4
  26. data/spec/dummy/app/resources/author.rb +0 -1
  27. data/spec/dummy/app/resources/book.rb +0 -1
  28. data/spec/dummy/app/resources/dog.rb +3 -3
  29. data/spec/dummy/app/resources/meeting.rb +1 -2
  30. data/spec/dummy/config/application.rb +7 -36
  31. data/spec/dummy/config/environments/production.rb +1 -1
  32. data/spec/dummy/config/initializers/morpheus.rb +1 -1
  33. data/spec/dummy/config/locales/en.yml +1 -1
  34. data/spec/dummy/config/routes.rb +0 -56
  35. data/spec/morpheus/associations/association_spec.rb +51 -33
  36. data/spec/morpheus/associations/belongs_to_association_spec.rb +14 -2
  37. data/spec/morpheus/associations/has_many_association_spec.rb +31 -11
  38. data/spec/morpheus/associations/has_one_association_spec.rb +14 -2
  39. data/spec/morpheus/base_spec.rb +104 -100
  40. data/spec/morpheus/client/associations_spec.rb +43 -36
  41. data/spec/morpheus/client/log_subscriber_spec.rb +33 -0
  42. data/spec/morpheus/client/railtie_spec.rb +5 -0
  43. data/spec/morpheus/configuration_spec.rb +92 -113
  44. data/spec/morpheus/mixins/associations_spec.rb +90 -108
  45. data/spec/morpheus/mixins/attributes_spec.rb +71 -77
  46. data/spec/morpheus/mixins/conversion_spec.rb +49 -59
  47. data/spec/morpheus/mixins/filtering_spec.rb +13 -0
  48. data/spec/morpheus/mixins/finders_spec.rb +180 -217
  49. data/spec/morpheus/mixins/introspection_spec.rb +81 -124
  50. data/spec/morpheus/mixins/persistence_spec.rb +140 -133
  51. data/spec/morpheus/mixins/{reflection_spec.rb → reflections_spec.rb} +28 -28
  52. data/spec/morpheus/mixins/request_handling_spec.rb +21 -0
  53. data/spec/morpheus/mixins/response_parsing_spec.rb +10 -2
  54. data/spec/morpheus/mixins/url_support_spec.rb +29 -0
  55. data/spec/morpheus/reflection_spec.rb +21 -0
  56. data/spec/morpheus/relation_spec.rb +34 -58
  57. data/spec/morpheus/request_cache_spec.rb +33 -2
  58. data/spec/morpheus/request_queue_spec.rb +37 -0
  59. data/spec/morpheus/request_spec.rb +102 -1
  60. data/spec/morpheus/response_parser_spec.rb +17 -0
  61. data/spec/morpheus/response_spec.rb +55 -51
  62. data/spec/morpheus/type_caster_spec.rb +128 -118
  63. data/spec/morpheus/url_builder_spec.rb +41 -0
  64. data/spec/shared/active_model_lint_test.rb +2 -2
  65. data/spec/spec_helper.rb +7 -14
  66. data/spec/support/configuration.rb +3 -4
  67. metadata +32 -16
  68. data/README.rdoc +0 -44
  69. data/autotest/discover.rb +0 -7
  70. data/lib/morpheus/mock.rb +0 -66
  71. data/spec/morpheus/mock_spec.rb +0 -133
data/.autotest ADDED
@@ -0,0 +1,12 @@
1
+ Autotest.add_discovery { 'rspec2' }
2
+
3
+ Autotest.add_hook :initialize do |at|
4
+ at.clear_mappings
5
+ ['.git', 'spec/dummy/log'].each { |exception| at.add_exception(exception) }
6
+
7
+ at.add_mapping(%r%^lib/(.*)\.rb$%) do |_, m|
8
+ at.files_matching %r%^spec/#{m[1]}_spec\.rb$%
9
+ end
10
+
11
+ at.add_mapping(%r%^spec/.*\_spec\.rb$%) { |filename, _| filename }
12
+ end
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Morpheus
2
+ ## A RESTful API Client library built over Typhoeus
3
+ * The project is working toward a DSL that is very ActiveResource/ActiveRecord-like with some features stolen from other great libraries like DataMapper.
4
+ * We started working on this at a point when we felt we were pushing ActiveResource to its limits.
5
+ * We wanted a little more flexibility in terms of API coupling.
6
+ * We have plans to make Morpheus as flexible as possible in terms of query interface, and resource representation.
7
+ * Additionally, Morpheus is built on top of Typhoeus and will be building in intuitive interfaces for working with parallel requests.
8
+
9
+ ```Ruby
10
+ class Dummy < Morpheus::Base
11
+ property :name
12
+ property :created_at
13
+ property :has_smarts
14
+
15
+ belongs_to :thingy
16
+ has_many :thingamabobs
17
+ has_one :doohickey
18
+ end
19
+ ```
20
+
21
+ ## Creating new resources
22
+ * Morpheus uses ActiveModel and so has an interface that tries to not stray too far from the similar ActiveRecord interface.
23
+
24
+ ```Ruby
25
+ @dummy = Dummy.new(
26
+ :name => 'Dumb',
27
+ :has_smarts => false,
28
+ :thingy_id => 2) # => #<Dummy:0x1016858d8>
29
+ @dummy.new_record? # => true
30
+ @dummy.save # => POST http://example.com/dummies
31
+ ```
32
+
33
+ ## Finding existing resources
34
+ * Resources can be retrieved with as little as their classname and unique identifier.
35
+
36
+ ```Ruby
37
+ @dummy = Dummy.find(1) # => GET http:example.com/dummies/1
38
+ @dummy.new_record? # => false
39
+ @dummy.name # => 'Dumb'
40
+ ```
41
+
42
+ ## Updating existing resources
43
+ * Resources can be updated just in the same way you would expect from a Active* library.
44
+
45
+ ```Ruby
46
+ @dummy.name = 'Dumber' # => 'Dumber'
47
+ @dummy.save # => PUT http://example.com/dummies/1
48
+ @dummy.update_attributes(:has_smarts => true) # => PUT http://example.com/dummies/1
49
+ ```
50
+
51
+ ## Associations
52
+ * Morpheus was originally conceived at a point when we needed ActiveResource with a little more.
53
+ * Associations are a bit of that 'little more'.
54
+
55
+ ```Ruby
56
+ @dummy.thingy # => GET http://example.com/thingies/2
57
+ @dummy.thingamabobs # => GET http://example.com/thingamabobs?dummy_id=1
58
+ @dummy.doohickey # => GET http://example.com/doohickey?dummy_id=1
59
+ ```
60
+
61
+ ## Query Interface
62
+ * ARel has a beautiful query interface.
63
+ * We have ported its method-chaining style to fit our query interface.
64
+
65
+ ```Ruby
66
+ Dummy.all # => GET http://example.com/dummies
67
+ Dummy.find(1,2,3) # => GET http://example.com/dummies?ids=1,2,3
68
+
69
+ Dummy.where(:name => 'Dumb') # => GET http://example.com/dummies?name=Dumb
70
+ Dummy.where(:name => 'Dumb', :has_smarts => false) # => GET http://example.com/dummies?name=Dumb&has_smarts=false
71
+ Dummy.limit(1) # => GET http://example.com/dummies?limit=1
72
+ Dummy.result_per_page = 25 # => 25
73
+ Dummy.page(3) # => GET http://example.com/dummies?limit=25&offset=50
74
+ ```
75
+
76
+ ## More about the project
77
+ Design informed by Service-Oriented Design with Ruby and Rails by Paul Dix, [Amazon](http://www.amazon.com/Service-Oriented-Design-Rails-Addison-Wesley-Professional/dp/0321659368)
data/Rakefile CHANGED
@@ -1,2 +1,18 @@
1
1
  #!/usr/bin/env rake
2
2
  require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc 'Default: run specs.'
6
+ task :default => :spec
7
+
8
+ desc 'Run specs'
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = './spec/**/*_spec.rb'
11
+ end
12
+
13
+ desc 'Generate code coverage'
14
+ RSpec::Core::RakeTask.new(:coverage) do |t|
15
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
16
+ t.rcov = true
17
+ t.rcov_opts = ['--exclude', 'spec']
18
+ end
data/lib/morpheus.rb CHANGED
@@ -59,8 +59,8 @@ end
59
59
 
60
60
  # The Railtie loads the LogSubscriber for printing output to the Rails log, and
61
61
  # Associations which plug-in to ActiveRecord to link associated resources.
62
- require 'morpheus/client/railtie' if defined?(Rails)
63
- require 'morpheus/client/inflections'
62
+ require 'morpheus/client/railtie' if defined?(Rails)
63
+ require 'morpheus/client/inflections' if defined?(ActiveSupport)
64
64
 
65
65
  # There are some Typhoeus patches contained here.
66
66
  require 'ext/typhoeus'
data/lib/morpheus/base.rb CHANGED
@@ -41,7 +41,7 @@ module Morpheus
41
41
  end
42
42
  end
43
43
 
44
- def ==(comparison_object)
44
+ def ==(comparison_object)
45
45
  comparison_object.equal?(self) ||
46
46
  (comparison_object.instance_of?(self.class) && comparison_object.id == id && !comparison_object.new_record?)
47
47
  end
@@ -1,3 +1,3 @@
1
1
  ActiveSupport::Inflector.inflections do |inflect|
2
2
  inflect.singular /^(.+)ss$/i, '\1ss'
3
- end
3
+ end
@@ -1,48 +1,46 @@
1
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
2
+ module Configuration
3
+ extend self
45
4
 
5
+ attr_accessor :username, :password
6
+
7
+ # Sets and gets the remote host service domain.
8
+ # The domain must be set before any requests can be made.
9
+ attr_writer :host
10
+ def host
11
+ @host || raise(Morpheus::ConfigurationError,
12
+ '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?
46
44
  end
47
45
 
48
46
  end
@@ -14,7 +14,7 @@ module Morpheus
14
14
  # Occurs when the remote host is unreachable.
15
15
  class RemoteHostConnectionFailure < ::StandardError
16
16
  end
17
-
17
+
18
18
  # Occurs when there is a server error resulting in a 500 response code
19
19
  class ServerError < ::StandardError
20
20
  end
@@ -5,7 +5,9 @@ module Morpheus
5
5
  autoload :HasManyAssociation, 'morpheus/associations/has_many_association'
6
6
  autoload :HasOneAssociation, 'morpheus/associations/has_one_association'
7
7
 
8
- extend ActiveSupport::Concern
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
9
11
 
10
12
  module ClassMethods
11
13
 
@@ -1,6 +1,9 @@
1
1
  module Morpheus
2
2
  module Attributes
3
- extend ActiveSupport::Concern
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
4
7
 
5
8
  module ClassMethods
6
9
 
@@ -40,94 +43,90 @@ module Morpheus
40
43
 
41
44
  end
42
45
 
43
- module InstanceMethods
46
+ def attributes
47
+ @attributes ||= self.class.default_attributes.dup
48
+ end
44
49
 
45
- def attributes
46
- @attributes ||= self.class.default_attributes.dup
47
- end
50
+ def read_attribute_for_validation(key)
51
+ attributes[key]
52
+ end
48
53
 
49
- def read_attribute_for_validation(key)
50
- attributes[key]
51
- end
54
+ def typecast(attribute, value)
55
+ TypeCaster.cast(value, self.class.typecast_attributes[attribute.to_sym])
56
+ end
52
57
 
53
- def typecast(attribute, value)
54
- TypeCaster.cast(value, self.class.typecast_attributes[attribute.to_sym])
58
+ def attributes_without_basic_attributes
59
+ attributes_to_reject = %w( id errors valid )
60
+ self.class.reflections.keys.each do |key|
61
+ attributes_to_reject.push(key.to_s)
55
62
  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
63
+ attributes.reject do |key, value|
64
+ attributes_to_reject.include?(key)
65
65
  end
66
+ end
66
67
 
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
68
+ def update_attribute(attribute, value)
69
+ reflection = self.class.reflect_on_association(attribute)
70
+ if reflection
71
+ update_reflection(reflection, attribute, value)
72
+ else
73
+ attributes[attribute.to_sym] = typecast(attribute, value)
74
74
  end
75
+ end
75
76
 
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
77
+ def update_reflection(reflection, attribute, value)
78
+ return unless value
79
+ if reflection.macro == :has_many # need to construct each member of array one-by-one
80
+ association_object = send(attribute)
81
+ value.each do |a_value|
82
+ if a_value.instance_of? reflection.klass
83
+ target = a_value
91
84
  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
85
+ target = reflection.build_association(a_value)
99
86
  end
100
- send("#{attribute}=", target)
87
+ association_object << target
88
+ end
89
+ elsif reflection.macro == :belongs_to
90
+ if value.instance_of? reflection.klass
91
+ target = value
101
92
  else
102
- if value.instance_of? reflection.klass
103
- target = value
93
+ if reflection.options[:polymorphic]
94
+ polymorphic_class = send("#{reflection.name}_type".to_sym)
95
+ polymorphic_class = value['type'] if value.include?('type')
96
+ target = polymorphic_class.constantize.new(value)
104
97
  else
105
98
  target = reflection.build_association(value)
106
99
  end
107
- send("#{attribute}=", target)
108
100
  end
101
+ send("#{attribute}=", target)
102
+ else
103
+ if value.instance_of? reflection.klass
104
+ target = value
105
+ else
106
+ target = reflection.build_association(value)
107
+ end
108
+ send("#{attribute}=", target)
109
109
  end
110
+ end
110
111
 
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
112
+ def merge_attributes(new_attributes)
113
+ new_attributes.each do |key, value|
114
+ case key.to_sym
115
+ when :errors
116
+ value.each do |k, v|
117
+ v.each do |message|
118
+ errors.add(k, message)
119
119
  end
120
- when :valid
121
- @valid = value
122
- else
123
- update_attribute(key, value)
124
120
  end
121
+ when :valid
122
+ @valid = value
123
+ else
124
+ update_attribute(key, value)
125
125
  end
126
- self
127
126
  end
128
- private :merge_attributes
129
-
127
+ self
130
128
  end
129
+ private :merge_attributes
131
130
 
132
131
  end
133
132
  end
@@ -1,21 +1,17 @@
1
1
  module Morpheus
2
2
  module Conversion
3
- extend ActiveSupport::Concern
4
3
 
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
4
+ def to_model
5
+ self
6
+ end
14
7
 
15
- def to_key
16
- attributes.keys if persisted?
17
- end
8
+ def to_param
9
+ id.to_s unless new_record?
10
+ end
18
11
 
12
+ def to_key
13
+ attributes.keys if persisted?
19
14
  end
15
+
20
16
  end
21
17
  end