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 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
- * JSON (objects returned as Ruby Hash)
11
- * XML (objects returned as Nokogiri Xml object)
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
- * 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)
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
- * 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)
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
- * base access stuff
44
- * defining attributes
45
- * defining relationships
46
- * testing the model
43
+ * base access stuff
44
+ * defining attributes
45
+ * defining associations
46
+ * testing the model
47
47
 
48
- == Cacheing
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
- * 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.
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
- * Note: all cached requests will be prepended with "[CACHE]" in resource agent logs
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
- * Restful JSON / XML resources
75
- * Resourceful external associations
76
- * Using associations with non resourceful ruby class (such as an ActiveRecord model)
77
- * Screen scraping example (using mechanize to access a secured resource)
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:
@@ -38,7 +38,7 @@ module Resourceful
38
38
  end
39
39
  end
40
40
 
41
- unless "".respond_to?(:to_datetime)
41
+ unless "".respond_to?(:to_date)
42
42
  def to_date
43
43
  ::Date.civil(*::Date._parse(self, false).values_at(:year, :mon, :mday).map { |arg| arg || 0 }) rescue nil
44
44
  end
@@ -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
- instance_variable_set("@#{clean_name}", klass.send(find_method_name, :all, config, force))
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
- klass = class_name.resourceful_constantize
49
- raise ArgumentError, "belongs_to_resourceful :class_name '#{class_name}' is not defined" if klass.nil?
50
- unless self.respond_to?(foreign_key)
51
- raise ArgumentError, "belongs_to_resourceful requires a '#{foreign_key}' method defined to return the foreign_key"
52
- end
53
- unless klass.respond_to?(find_method_name)
54
- raise NotImplementedError, "belongs_to_resourceful expects #{klass} to implement a Findable method named '#{find_method_name}'"
55
- end
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
- instance_variable_set("@#{clean_name}", klass.send(find_method_name, :all, config, force))
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
- klass = self.class.get_namespaced_klass(class_name)
49
- raise ArgumentError, "belongs_to :class '#{class_name}' is not defined in any given namespaces" if klass.nil?
50
- unless self.respond_to?(foreign_key)
51
- raise ArgumentError, "belongs_to requires a '#{foreign_key}' method defined to return the foreign_key"
52
- end
53
- unless klass.respond_to?(find_method_name)
54
- raise NotImplementedError, "belongs_to expects #{klass} to implement a Findable method named '#{find_method_name}'"
55
- end
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
- # Helpers
55
+
56
+
57
+
58
+
59
+ private # Helpers ***************************************
59
60
 
60
61
  def should_have_resourceful_typed_attribute(name, type)
61
62
  if type
@@ -3,7 +3,7 @@ module Resourceful
3
3
 
4
4
  MAJOR = 0
5
5
  MINOR = 7
6
- TINY = 21
6
+ TINY = 22
7
7
 
8
8
  def self.to_s # :nodoc:
9
9
  [MAJOR, MINOR, TINY].join('.')
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.21
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-03 00:00:00 -05:00
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