kelredd-resourceful 0.7.21 → 0.7.22
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +23 -21
- data/lib/resourceful/extensions.rb +1 -1
- data/lib/resourceful/model/activerecord_associations.rb +23 -30
- data/lib/resourceful/model/eager_load.rb +43 -0
- data/lib/resourceful/model/external_associations.rb +37 -30
- data/lib/resourceful/resource/cache.rb +6 -0
- data/lib/resourceful/shoulda/test_unit.rb +5 -4
- data/lib/resourceful/version.rb +1 -1
- metadata +3 -2
data/README.rdoc
CHANGED
@@ -7,12 +7,12 @@ A ruby gem to abstract web resource handling.
|
|
7
7
|
The key to resourceful is being able to interact with web resources using custom defined objects instead of the raw data. Unlike ActiveResource, Resourceful needs no rigid restful API backing its models. All you need is data in either JSON or XML (or HTML), and be able to creatively define models from that data.
|
8
8
|
|
9
9
|
The supported formats are:
|
10
|
-
|
11
|
-
|
10
|
+
* JSON (objects returned as Ruby Hash)
|
11
|
+
* XML (objects returned as Nokogiri Xml object)
|
12
12
|
|
13
13
|
Resourceful uses agents to fetch and push data to and from web resources. Supported agents are:
|
14
|
-
|
15
|
-
|
14
|
+
* rest_client: great for interacting with Restful resources
|
15
|
+
* mechanize: great for interacting with raw resources, or secured resources (and for screen scraping raw html)
|
16
16
|
|
17
17
|
== Installation
|
18
18
|
|
@@ -20,11 +20,11 @@ Resourceful uses agents to fetch and push data to and from web resources. Suppo
|
|
20
20
|
|
21
21
|
== Dependencies
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
* nokogiri (xml resource format handling)
|
24
|
+
* json (json resource format handling)
|
25
|
+
* rest_client (restful web resource access agent)
|
26
|
+
* mechanize (more raw web resource access agent; access secured resources)
|
27
|
+
* log4r (for resource interaction logging)
|
28
28
|
|
29
29
|
== Basic Usage
|
30
30
|
|
@@ -40,25 +40,25 @@ Resourceful uses agents to fetch and push data to and from web resources. Suppo
|
|
40
40
|
So it's all about the models, right. So you have this data resource on the web: a JSON API, an XML feed, an old crappy HTML page, a secured page with personal data you want to access. You start noticing attributes and relationships in this data - it's time to define some models. Enter resourceful. Resourceful gives you fancy pants ways to define and test some models, without bothering with all the kruft of accessing those resources via the web. Check it:
|
41
41
|
|
42
42
|
# TODO: show examples of
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
* base access stuff
|
44
|
+
* defining attributes
|
45
|
+
* defining associations
|
46
|
+
* testing the model
|
47
47
|
|
48
|
-
==
|
48
|
+
== Caching
|
49
49
|
|
50
50
|
Resourceful provides resource caching by default, both at the model level and at the agent level.
|
51
51
|
|
52
52
|
=== Model Caching
|
53
53
|
|
54
54
|
Model caching refers to using the instance of the model to store attribute and association values. Resourceful, by default, caches all attribute values and associations in the model instance. Both attribute values and association values can be "reloaded" by passing true as a single parameter to the attribute or association method. Please note:
|
55
|
-
|
56
|
-
|
55
|
+
* For attribute values, "reloading" simply means parsing the value from the existing raw agent data. The resource is not fetched via the web for attribute reloading.
|
56
|
+
* For associations, "reloading" means calling out for the resource and fetching it from the web. This bypasses any agent caching and forces the resource data to be downloaded.
|
57
57
|
|
58
58
|
=== Agent Caching
|
59
59
|
|
60
60
|
Agent caching refers to using a built in cache to store web resource responses for future access. Resourceful, by default, caches all web requests made via the agents. Resource responses are cached in memory, based on a unique key, and expire, by default, after 60 seconds. Custom expiration periods can be specified. This improves performance when repetitively accessing the same resource, while ensuring the resource does not become stale. Caching can be bypassed on any request by forcing data download using the :force => true option in agent or model data access methods.
|
61
|
-
|
61
|
+
* Note: all cached requests will be prepended with "[CACHE]" in resource agent logs
|
62
62
|
|
63
63
|
== Logging
|
64
64
|
|
@@ -71,10 +71,12 @@ Resourceful provides resource agent logging for you by default. Log information
|
|
71
71
|
== Examples
|
72
72
|
|
73
73
|
# TODO: add in examples:
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
* Screen scraping example (using mechanize to access a secured resource)
|
75
|
+
* Restful JSON / XML resources
|
76
|
+
* Resourceful external associations
|
77
|
+
* Resourceful embedded associations
|
78
|
+
* Using associations with non resourceful ruby class (such as an ActiveRecord model)
|
79
|
+
* Association eager loading
|
78
80
|
|
79
81
|
== Testing the Resourceful gem
|
80
82
|
A suite of cucumber features are available for you to run as acceptance tests. You should look to the features for additional documentation and usage scenarios. To run the features:
|
@@ -6,10 +6,6 @@ module Resourceful
|
|
6
6
|
|
7
7
|
protected
|
8
8
|
|
9
|
-
def has_one_resourceful(name, config={})
|
10
|
-
has_many(name, config).first
|
11
|
-
end
|
12
|
-
|
13
9
|
def has_many_resourceful(name, config={})
|
14
10
|
clean_name = Resourceful::Model::Base.cleanup_name(name)
|
15
11
|
config ||= {}
|
@@ -17,18 +13,18 @@ module Resourceful
|
|
17
13
|
class_name = config.delete(:class_name).to_s
|
18
14
|
find_method_name = (config.delete(:method) || 'find').to_s
|
19
15
|
force = config.delete(:force) || false
|
16
|
+
foreign_key_name = config.delete(:foreign_key) || "#{self.name.demodulize.underscore}_id"
|
17
|
+
foreign_key_method = config.delete(:foreign_key_method) || 'id'
|
20
18
|
define_method(name) do |*args|
|
21
19
|
reload = args.first || false
|
22
|
-
klass = class_name.resourceful_constantize
|
23
|
-
raise ArgumentError, "has_many_resourceful :class_name '#{class_name}' is not defined" if klass.nil?
|
24
|
-
unless klass.respond_to?(find_method_name)
|
25
|
-
raise NotImplementedError, "has_many_resourceful expects #{klass} to implement a Findable method named '#{find_method_name}'"
|
26
|
-
end
|
27
|
-
fk = config.delete(:foreign_key) || "#{self.class.name.demodulize.underscore}_id"
|
28
|
-
fk_method = config.delete(:foreign_key_method) || 'id'
|
29
|
-
config[fk] = self.send(fk_method)
|
30
20
|
if reload || (assoc_val = instance_variable_get("@#{clean_name}")).nil?
|
31
|
-
|
21
|
+
klass = class_name.resourceful_constantize
|
22
|
+
raise ArgumentError, "has_many_resourceful :class_name '#{class_name}' is not defined" if klass.nil?
|
23
|
+
unless klass.respond_to?(find_method_name)
|
24
|
+
raise NotImplementedError, "has_many_resourceful expects #{klass} to implement a Findable method named '#{find_method_name}'"
|
25
|
+
end
|
26
|
+
config[foreign_key_name] = self.send(foreign_key_method)
|
27
|
+
instance_variable_set("@#{clean_name}", klass.send(find_method_name, :all, config, reload || force))
|
32
28
|
else
|
33
29
|
assoc_val
|
34
30
|
end
|
@@ -40,28 +36,25 @@ module Resourceful
|
|
40
36
|
config ||= {}
|
41
37
|
raise ArgumentError, "belongs_to_resourceful requires a :class_name option to be specified" unless config[:class_name]
|
42
38
|
class_name = config.delete(:class_name).to_s
|
43
|
-
foreign_key = config.delete(:foreign_key) || "#{clean_name}_id"
|
44
39
|
find_method_name = (config.delete(:method) || 'find').to_s
|
45
40
|
force = config.delete(:force) || false
|
41
|
+
foreign_key_name = config.delete(:foreign_key_name) || 'id'
|
42
|
+
foreign_key_method = config.delete(:foreign_key) || "#{clean_name}_id"
|
46
43
|
define_method(name) do |*args|
|
47
44
|
reload = args.first || false
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
fk = self.send(foreign_key)
|
57
|
-
if fk.nil? || (fk.respond_to?('empty?') && fk.empty?)
|
58
|
-
nil
|
59
|
-
else
|
60
|
-
if reload || (assoc_val = instance_variable_get("@#{clean_name}")).nil?
|
61
|
-
instance_variable_set("@#{clean_name}", klass.send(find_method_name, fk, config, force))
|
62
|
-
else
|
63
|
-
assoc_val
|
45
|
+
if reload || (assoc_val = instance_variable_get("@#{clean_name}")).nil?
|
46
|
+
klass = class_name.resourceful_constantize
|
47
|
+
raise ArgumentError, "belongs_to_resourceful :class_name '#{class_name}' is not defined" if klass.nil?
|
48
|
+
unless self.respond_to?(foreign_key_method)
|
49
|
+
raise ArgumentError, "belongs_to_resourceful requires a '#{foreign_key_method}' method defined to return the foreign_key"
|
50
|
+
end
|
51
|
+
unless klass.respond_to?(find_method_name)
|
52
|
+
raise NotImplementedError, "belongs_to_resourceful expects #{klass} to implement a Findable method named '#{find_method_name}'"
|
64
53
|
end
|
54
|
+
fk = self.send(foreign_key_method)
|
55
|
+
instance_variable_set("@#{clean_name}", fk.nil? || (fk.respond_to?('empty?') && fk.empty?) ? nil : klass.send(find_method_name, fk, config, reload || force))
|
56
|
+
else
|
57
|
+
assoc_val
|
65
58
|
end
|
66
59
|
end
|
67
60
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Resourceful
|
2
|
+
|
3
|
+
# The idea here is to take a collection of object instances that have resourceful associations and
|
4
|
+
# => for each association specified
|
5
|
+
# => get the foreign key name
|
6
|
+
# => get the foreign key values for each item in the collection
|
7
|
+
# => grab the data for all the foreign keys in one call
|
8
|
+
# => build object for each result in data
|
9
|
+
# => for each item in the collection, set it's association value from built objects
|
10
|
+
def self.eager_load(collection, associations)
|
11
|
+
if (collection.respond_to?('each') && (collection.respond_to?('empty?') && !collection.empty?)) && \
|
12
|
+
(associations.respond_to?('each') && (associations.respond_to?('empty?') && !associations.empty?))
|
13
|
+
klass = collection.first.class
|
14
|
+
associations.each do |association_name|
|
15
|
+
clean_association_name = Resourceful::Model::Base.cleanup_name(association_name.to_s)
|
16
|
+
assoc_data = get_association_data(klass, clean_association_name)
|
17
|
+
assoc_klass = klass.ancestors.include?(Resourceful::Model::Base) ? klass.get_namespaced_klass(assoc_data[:class_name]) : assoc_data[:class_name].resourceful_constantize
|
18
|
+
fk_values = collection.inject([]) {|vals, item| vals << item.send(assoc_data[:foreign_key_method])}
|
19
|
+
fk_results = assoc_klass.send(assoc_data[:find_method_name], :all, {assoc_data[:foreign_key_name] => fk_values.join(',')}, true)
|
20
|
+
collection.each do |item|
|
21
|
+
item_results = fk_results.reject{|result| result.send(assoc_data[:foreign_key_name]) != item.send(assoc_data[:foreign_key_method])}
|
22
|
+
item.instance_variable_set("@#{clean_association_name}", [:belongs_to, :has_one].include?(assoc_data[:type]) ? item_results.first : item_results)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
collection
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add_to_associations(klass_name, name, data)
|
30
|
+
@@resourceful_associations ||= {}
|
31
|
+
@@resourceful_associations[klass_name] ||= {}
|
32
|
+
@@resourceful_associations[klass_name][name] = data
|
33
|
+
end
|
34
|
+
def self.get_association_data(klass, name)
|
35
|
+
@@resourceful_associations ||= {}
|
36
|
+
assoc_data = nil
|
37
|
+
klass.ancestors.each do |anc|
|
38
|
+
break if @@resourceful_associations[anc.to_s] && (assoc_data = @@resourceful_associations[anc.to_s][name])
|
39
|
+
end
|
40
|
+
assoc_data
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -6,10 +6,6 @@ module Resourceful
|
|
6
6
|
|
7
7
|
protected
|
8
8
|
|
9
|
-
def has_one(name, config={})
|
10
|
-
has_many(name, config).first
|
11
|
-
end
|
12
|
-
|
13
9
|
def has_many(name, config={})
|
14
10
|
clean_name = cleanup_name(name)
|
15
11
|
config ||= {}
|
@@ -17,18 +13,25 @@ module Resourceful
|
|
17
13
|
class_name = config.delete(:class).to_s
|
18
14
|
find_method_name = (config.delete(:method) || 'find').to_s
|
19
15
|
force = config.delete(:force) || false
|
16
|
+
foreign_key_name = config.delete(:foreign_key) || "#{self.name.demodulize.underscore}_id"
|
17
|
+
foreign_key_method = config.delete(:foreign_key_method) || 'id'
|
18
|
+
Resourceful.add_to_associations(self.name, clean_name, {
|
19
|
+
:type => :has_many,
|
20
|
+
:class_name => class_name,
|
21
|
+
:foreign_key_name => foreign_key_name,
|
22
|
+
:foreign_key_method => foreign_key_method,
|
23
|
+
:find_method_name => find_method_name
|
24
|
+
})
|
20
25
|
define_method(name) do |*args|
|
21
26
|
reload = args.first || false
|
22
|
-
klass = self.class.get_namespaced_klass(class_name)
|
23
|
-
raise ArgumentError, "has_many :class '#{class_name}' is not defined in any given namespaces" if klass.nil?
|
24
|
-
unless klass.respond_to?(find_method_name)
|
25
|
-
raise NotImplementedError, "has_many expects #{klass} to implement a Findable method named '#{find_method_name}'"
|
26
|
-
end
|
27
|
-
fk = config.delete(:foreign_key) || "#{self.class.name.demodulize.underscore}_id"
|
28
|
-
fk_method = config.delete(:foreign_key_method) || 'id'
|
29
|
-
config[fk] = self.send(fk_method)
|
30
27
|
if reload || (assoc_val = instance_variable_get("@#{clean_name}")).nil?
|
31
|
-
|
28
|
+
klass = self.class.get_namespaced_klass(class_name)
|
29
|
+
raise ArgumentError, "has_many :class '#{class_name}' is not defined in any given namespaces" if klass.nil?
|
30
|
+
unless klass.respond_to?(find_method_name)
|
31
|
+
raise NotImplementedError, "has_many expects #{klass} to implement a Findable method named '#{find_method_name}'"
|
32
|
+
end
|
33
|
+
config[foreign_key_name] = self.send(foreign_key_method)
|
34
|
+
instance_variable_set("@#{clean_name}", klass.send(find_method_name, :all, config, reload || force))
|
32
35
|
else
|
33
36
|
assoc_val
|
34
37
|
end
|
@@ -40,28 +43,32 @@ module Resourceful
|
|
40
43
|
config ||= {}
|
41
44
|
raise ArgumentError, "belongs_to requires a :class option to be specified" unless config[:class]
|
42
45
|
class_name = config.delete(:class).to_s
|
43
|
-
foreign_key = config.delete(:foreign_key) || "#{clean_name}_id"
|
44
46
|
find_method_name = (config.delete(:method) || 'find').to_s
|
45
47
|
force = config.delete(:force) || false
|
48
|
+
foreign_key_name = config.delete(:foreign_key_name) || 'id'
|
49
|
+
foreign_key_method = config.delete(:foreign_key) || "#{clean_name}_id"
|
50
|
+
Resourceful.add_to_associations(self.name, clean_name, {
|
51
|
+
:type => :belongs_to,
|
52
|
+
:class_name => class_name,
|
53
|
+
:foreign_key_name => foreign_key_name,
|
54
|
+
:foreign_key_method => foreign_key_method,
|
55
|
+
:find_method_name => find_method_name
|
56
|
+
})
|
46
57
|
define_method(name) do |*args|
|
47
58
|
reload = args.first || false
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
fk = self.send(foreign_key)
|
57
|
-
if fk.nil? || (fk.respond_to?('empty?') && fk.empty?)
|
58
|
-
nil
|
59
|
-
else
|
60
|
-
if reload || (assoc_val = instance_variable_get("@#{clean_name}")).nil?
|
61
|
-
instance_variable_set("@#{clean_name}", klass.send(find_method_name, fk, config, force))
|
62
|
-
else
|
63
|
-
assoc_val
|
59
|
+
if reload || (assoc_val = instance_variable_get("@#{clean_name}")).nil?
|
60
|
+
klass = self.class.get_namespaced_klass(class_name)
|
61
|
+
raise ArgumentError, "belongs_to :class '#{class_name}' is not defined in any given namespaces" if klass.nil?
|
62
|
+
unless self.respond_to?(foreign_key_method)
|
63
|
+
raise ArgumentError, "belongs_to requires a '#{foreign_key_method}' method defined to return the foreign_key"
|
64
|
+
end
|
65
|
+
unless klass.respond_to?(find_method_name)
|
66
|
+
raise NotImplementedError, "belongs_to expects #{klass} to implement a Findable method named '#{find_method_name}'"
|
64
67
|
end
|
68
|
+
fk = self.send(foreign_key_method)
|
69
|
+
instance_variable_set("@#{clean_name}", fk.nil? || (fk.respond_to?('empty?') && fk.empty?) ? nil : klass.send(find_method_name, fk, config, reload || force))
|
70
|
+
else
|
71
|
+
assoc_val
|
65
72
|
end
|
66
73
|
end
|
67
74
|
end
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module Resourceful
|
2
2
|
module Resource
|
3
|
+
|
4
|
+
# The idea here is to put Resourceful into an eager loading "mode" while yielding to a block
|
5
|
+
# => while in this mode, resourceful models will try to load and cache data in batches
|
6
|
+
#def self.eager_load
|
7
|
+
# @@
|
8
|
+
#end
|
3
9
|
class Cache
|
4
10
|
|
5
11
|
attr_reader :store, :expiration
|
@@ -52,10 +52,11 @@ module Resourceful
|
|
52
52
|
should_have_instance_methods clean_name
|
53
53
|
should_have_resourceful_association_collection(name, opts)
|
54
54
|
end
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
private # Helpers ***************************************
|
59
60
|
|
60
61
|
def should_have_resourceful_typed_attribute(name, type)
|
61
62
|
if type
|
data/lib/resourceful/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kelredd-resourceful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.22
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kelly Redding
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-06 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -82,6 +82,7 @@ files:
|
|
82
82
|
- lib/resourceful/model/activerecord_associations.rb
|
83
83
|
- lib/resourceful/model/attribute_types.rb
|
84
84
|
- lib/resourceful/model/base.rb
|
85
|
+
- lib/resourceful/model/eager_load.rb
|
85
86
|
- lib/resourceful/model/embedded_associations.rb
|
86
87
|
- lib/resourceful/model/external_associations.rb
|
87
88
|
- lib/resourceful/model/findable.rb
|