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.
- data/.autotest +12 -0
- data/.rspec +1 -0
- data/README.md +77 -0
- data/Rakefile +16 -0
- data/lib/morpheus.rb +2 -2
- data/lib/morpheus/base.rb +1 -1
- data/lib/morpheus/client/inflections.rb +1 -1
- data/lib/morpheus/configuration.rb +41 -43
- data/lib/morpheus/errors.rb +1 -1
- data/lib/morpheus/mixins/associations.rb +3 -1
- data/lib/morpheus/mixins/attributes.rb +66 -67
- data/lib/morpheus/mixins/conversion.rb +9 -13
- data/lib/morpheus/mixins/filtering.rb +4 -1
- data/lib/morpheus/mixins/finders.rb +4 -1
- data/lib/morpheus/mixins/introspection.rb +12 -16
- data/lib/morpheus/mixins/persistence.rb +25 -26
- data/lib/morpheus/mixins/reflections.rb +4 -1
- data/lib/morpheus/mixins/request_handling.rb +4 -1
- data/lib/morpheus/mixins/response_parsing.rb +12 -12
- data/lib/morpheus/mixins/url_support.rb +4 -1
- data/lib/morpheus/request.rb +34 -34
- data/lib/morpheus/response.rb +2 -2
- data/lib/morpheus/response_parser.rb +9 -4
- data/lib/morpheus/version.rb +1 -1
- data/morpheus.gemspec +4 -4
- data/spec/dummy/app/resources/author.rb +0 -1
- data/spec/dummy/app/resources/book.rb +0 -1
- data/spec/dummy/app/resources/dog.rb +3 -3
- data/spec/dummy/app/resources/meeting.rb +1 -2
- data/spec/dummy/config/application.rb +7 -36
- data/spec/dummy/config/environments/production.rb +1 -1
- data/spec/dummy/config/initializers/morpheus.rb +1 -1
- data/spec/dummy/config/locales/en.yml +1 -1
- data/spec/dummy/config/routes.rb +0 -56
- data/spec/morpheus/associations/association_spec.rb +51 -33
- data/spec/morpheus/associations/belongs_to_association_spec.rb +14 -2
- data/spec/morpheus/associations/has_many_association_spec.rb +31 -11
- data/spec/morpheus/associations/has_one_association_spec.rb +14 -2
- data/spec/morpheus/base_spec.rb +104 -100
- data/spec/morpheus/client/associations_spec.rb +43 -36
- data/spec/morpheus/client/log_subscriber_spec.rb +33 -0
- data/spec/morpheus/client/railtie_spec.rb +5 -0
- data/spec/morpheus/configuration_spec.rb +92 -113
- data/spec/morpheus/mixins/associations_spec.rb +90 -108
- data/spec/morpheus/mixins/attributes_spec.rb +71 -77
- data/spec/morpheus/mixins/conversion_spec.rb +49 -59
- data/spec/morpheus/mixins/filtering_spec.rb +13 -0
- data/spec/morpheus/mixins/finders_spec.rb +180 -217
- data/spec/morpheus/mixins/introspection_spec.rb +81 -124
- data/spec/morpheus/mixins/persistence_spec.rb +140 -133
- data/spec/morpheus/mixins/{reflection_spec.rb → reflections_spec.rb} +28 -28
- data/spec/morpheus/mixins/request_handling_spec.rb +21 -0
- data/spec/morpheus/mixins/response_parsing_spec.rb +10 -2
- data/spec/morpheus/mixins/url_support_spec.rb +29 -0
- data/spec/morpheus/reflection_spec.rb +21 -0
- data/spec/morpheus/relation_spec.rb +34 -58
- data/spec/morpheus/request_cache_spec.rb +33 -2
- data/spec/morpheus/request_queue_spec.rb +37 -0
- data/spec/morpheus/request_spec.rb +102 -1
- data/spec/morpheus/response_parser_spec.rb +17 -0
- data/spec/morpheus/response_spec.rb +55 -51
- data/spec/morpheus/type_caster_spec.rb +128 -118
- data/spec/morpheus/url_builder_spec.rb +41 -0
- data/spec/shared/active_model_lint_test.rb +2 -2
- data/spec/spec_helper.rb +7 -14
- data/spec/support/configuration.rb +3 -4
- metadata +32 -16
- data/README.rdoc +0 -44
- data/autotest/discover.rb +0 -7
- data/lib/morpheus/mock.rb +0 -66
- 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'
|
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
@@ -1,48 +1,46 @@
|
|
1
1
|
module Morpheus
|
2
|
-
|
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
|
data/lib/morpheus/errors.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
46
|
+
def attributes
|
47
|
+
@attributes ||= self.class.default_attributes.dup
|
48
|
+
end
|
44
49
|
|
45
|
-
|
46
|
-
|
47
|
-
|
50
|
+
def read_attribute_for_validation(key)
|
51
|
+
attributes[key]
|
52
|
+
end
|
48
53
|
|
49
|
-
|
50
|
-
|
51
|
-
|
54
|
+
def typecast(attribute, value)
|
55
|
+
TypeCaster.cast(value, self.class.typecast_attributes[attribute.to_sym])
|
56
|
+
end
|
52
57
|
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
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
|
-
|
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
|
103
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|