morpheus 0.3.6 → 0.3.7

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