storage_room 0.2.1 → 0.3.0

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 (81) hide show
  1. data/History.txt +6 -0
  2. data/README.rdoc +10 -4
  3. data/Rakefile +3 -1
  4. data/TODO +7 -2
  5. data/VERSION +1 -1
  6. data/examples/authentication.rb +5 -3
  7. data/examples/create_entry.rb +8 -5
  8. data/examples/destroy_entry.rb +2 -2
  9. data/examples/get_collections.rb +2 -2
  10. data/examples/import_csv.rb +4 -4
  11. data/examples/search_entries.rb +2 -2
  12. data/examples/update_entry.rb +2 -2
  13. data/lib/console.rb +12 -0
  14. data/lib/storage_room.rb +73 -34
  15. data/lib/storage_room/accessors.rb +190 -0
  16. data/lib/storage_room/array.rb +3 -23
  17. data/lib/storage_room/embedded.rb +1 -1
  18. data/lib/storage_room/embeddeds/field.rb +28 -0
  19. data/lib/storage_room/embeddeds/fields/association_field.rb +17 -0
  20. data/lib/storage_room/embeddeds/fields/associations/many_association_field.rb +9 -0
  21. data/lib/storage_room/embeddeds/fields/associations/one_association_field.rb +9 -0
  22. data/lib/storage_room/embeddeds/fields/atomic/boolean_field.rb +6 -0
  23. data/lib/storage_room/embeddeds/fields/atomic/date_field.rb +6 -0
  24. data/lib/storage_room/embeddeds/fields/atomic/float_field.rb +6 -0
  25. data/lib/storage_room/embeddeds/fields/atomic/integer_field.rb +6 -0
  26. data/lib/storage_room/embeddeds/fields/atomic/string_field.rb +6 -0
  27. data/lib/storage_room/embeddeds/fields/atomic/time_field.rb +6 -0
  28. data/lib/storage_room/embeddeds/fields/atomic_field.rb +15 -0
  29. data/lib/storage_room/embeddeds/fields/compound/attachment_field.rb +6 -0
  30. data/lib/storage_room/embeddeds/fields/compound/file_field.rb +6 -0
  31. data/lib/storage_room/embeddeds/fields/compound/image_field.rb +6 -0
  32. data/lib/storage_room/embeddeds/fields/compound/location_field.rb +6 -0
  33. data/lib/storage_room/embeddeds/fields/compound_field.rb +10 -0
  34. data/lib/storage_room/embeddeds/file.rb +1 -1
  35. data/lib/storage_room/embeddeds/image.rb +6 -0
  36. data/lib/storage_room/embeddeds/location.rb +2 -1
  37. data/lib/storage_room/extensions/const_defined.rb +12 -0
  38. data/lib/storage_room/identity_map.rb +117 -0
  39. data/lib/storage_room/model.rb +19 -23
  40. data/lib/storage_room/models/collection.rb +76 -11
  41. data/lib/storage_room/models/entry.rb +6 -32
  42. data/lib/storage_room/plugins.rb +22 -0
  43. data/lib/storage_room/proxy.rb +49 -0
  44. data/lib/storage_room/{base.rb → resource.rb} +13 -36
  45. data/spec/fixtures/collection.json +0 -16
  46. data/spec/spec_helper.rb +1 -0
  47. data/spec/storage_room/accessors_spec.rb +107 -0
  48. data/spec/storage_room/array_spec.rb +13 -9
  49. data/spec/storage_room/embedded_spec.rb +5 -1
  50. data/spec/storage_room/embeddeds/field_spec.rb +25 -0
  51. data/spec/storage_room/embeddeds/fields/association_field_spec.rb +29 -0
  52. data/spec/storage_room/embeddeds/fields/associations/many_association_field_spec.rb +21 -0
  53. data/spec/storage_room/embeddeds/fields/associations/one_association_field_spec.rb +19 -0
  54. data/spec/storage_room/embeddeds/fields/atomic/boolean_field_spec.rb +5 -0
  55. data/spec/storage_room/embeddeds/fields/atomic/date_field_spec.rb +5 -0
  56. data/spec/storage_room/embeddeds/fields/atomic/float_field_spec.rb +5 -0
  57. data/spec/storage_room/embeddeds/fields/atomic/integer_field_spec.rb +5 -0
  58. data/spec/storage_room/embeddeds/fields/atomic/string_field_spec.rb +5 -0
  59. data/spec/storage_room/embeddeds/fields/atomic/time_field_spec.rb +5 -0
  60. data/spec/storage_room/embeddeds/fields/atomic_field_spec.rb +27 -0
  61. data/spec/storage_room/embeddeds/fields/compound/attachment_field_spec.rb +5 -0
  62. data/spec/storage_room/embeddeds/fields/compound/file_field_spec.rb +5 -0
  63. data/spec/storage_room/embeddeds/fields/compound/image_field_spec.rb +5 -0
  64. data/spec/storage_room/embeddeds/fields/compound/location_field_spec.rb +5 -0
  65. data/spec/storage_room/embeddeds/fields/compound_field_spec.rb +17 -0
  66. data/spec/storage_room/embeddeds/location_spec.rb +13 -1
  67. data/spec/storage_room/identity_map_spec.rb +53 -0
  68. data/spec/storage_room/model_spec.rb +70 -50
  69. data/spec/storage_room/models/collection_spec.rb +57 -14
  70. data/spec/storage_room/models/entry_spec.rb +16 -20
  71. data/spec/storage_room/proxy_spec.rb +58 -0
  72. data/spec/storage_room/resource_spec.rb +98 -0
  73. data/spec/storage_room_spec.rb +45 -9
  74. data/storage_room.gemspec +48 -9
  75. data/tasks/storage_room.rake +3 -0
  76. metadata +49 -10
  77. data/lib/storage_room/attributes.rb +0 -46
  78. data/lib/storage_room/field.rb +0 -8
  79. data/spec/storage_room/attributes_spec.rb +0 -82
  80. data/spec/storage_room/base_spec.rb +0 -118
  81. data/spec/storage_room/field_spec.rb +0 -5
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ === Version 0.3.0
2
+ * Complete refactoring of internals.
3
+ * Added support for associations
4
+ * Added support for identity map
5
+ * Added configurable Resources
6
+
1
7
  === Version 0.1.1
2
8
 
3
9
  * Added rdoc
data/README.rdoc CHANGED
@@ -1,6 +1,13 @@
1
1
  == StorageRoom Gem
2
2
 
3
- This gem provides read and write access to the StorageRoom API (http://storageroomapp.com). It has an ActiveRecord/ActiveModel like interface.
3
+ This gem provides read and write access to the StorageRoom API (http://storageroomapp.com).
4
+
5
+ == Main Features
6
+
7
+ * ActiveRecord/ActiveModel like interface.
8
+ * Automatic creation of Entry Classes from a Collection, you don't have to configure anything
9
+ * Supports lazy-loading of associations (e.g. post.category will fetch a category transparently if it has not yet been loaded)
10
+ * Supports caching through an identity map, so that Resources don't have to be loaded multiple times
4
11
 
5
12
 
6
13
  == Installation
@@ -17,10 +24,9 @@ This is a walkthrough with all steps you need to setup a devise entry, including
17
24
  StorageRoom.authenticate(YOUR_ACCOUNT_ID, YOUR_APPLICATION_API_KEY)
18
25
  collection = StorageRoom::Collection.find('4ddaf68b4d085d374a000003')
19
26
 
20
- entries = collection.entries
21
- entry = entries.resources.first
27
+ entry = collection.entries.resources.first # collection.entries contains meta information, the resources key contains the returned objects
22
28
 
23
- entry[:name] = 'Foobar'
29
+ entry.name = 'Foobar'
24
30
 
25
31
  if entry.save
26
32
  puts "Entry saved."
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ begin
12
12
  gem.authors = ["Sascha Konietzke"]
13
13
  gem.add_development_dependency "rspec", ">= 1.2.9"
14
14
  gem.add_development_dependency "webmock"
15
-
15
+
16
16
  gem.add_dependency 'httparty', '>= 0.6.1'
17
17
  gem.add_dependency 'activesupport', '>= 3.0.0'
18
18
 
@@ -45,3 +45,5 @@ Rake::RDocTask.new do |rdoc|
45
45
  rdoc.rdoc_files.include('README*')
46
46
  rdoc.rdoc_files.include('lib/**/*.rb')
47
47
  end
48
+
49
+ Dir['tasks/**/*.rake'].each { |t| load t }
data/TODO CHANGED
@@ -1,5 +1,10 @@
1
1
 
2
+
2
3
  == Version X
3
4
 
4
- - have collection fields
5
- - executable/cli
5
+ - have a gemfile?
6
+ - make on/many only accept objects of correct class?
7
+ - executable/cli
8
+ - file uploads/removals
9
+ - get files via api
10
+ - look at arguments with identity map
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.0
@@ -1,6 +1,8 @@
1
1
  require File.dirname(__FILE__) + '/../lib/storage_room'
2
2
 
3
- ACCOUNT_ID = '4cef9a8c425071fa6900002f' # your account id
4
- APPLICATION_API_KEY = 'c499kx9L6aBfvJlvSKbF' # your application's API key with read/write access
3
+ ACCOUNT_ID = '4dda7761b65245fde1000051' # your account id
4
+ APPLICATION_API_KEY = 'kCWTmS1wxYnxzJyteuIn' # your application's API key with read/write access
5
5
 
6
- StorageRoom.authenticate(ACCOUNT_ID, APPLICATION_API_KEY)
6
+ StorageRoom.authenticate(ACCOUNT_ID, APPLICATION_API_KEY)
7
+
8
+ StorageRoom.server = "api.lvh.me:3000"
@@ -2,13 +2,16 @@
2
2
 
3
3
  require File.dirname(__FILE__) + '/authentication'
4
4
 
5
+
5
6
  # fetch the collection first
6
- collection = StorageRoom::Collection.find('4ddaf68b4d085d374a000003')
7
+ guidebook_collection = StorageRoom::Collection.find('4dda7761b65245fde100005d')
8
+ category_collection = StorageRoom::Collection.find('4dda7761b65245fde100001a')
7
9
 
8
- entry2 = collection.entry_class.new(:title => 'Bar', :price => 2.23)
10
+ category = category_collection.entries.resources.first # find the first category
11
+ guidebook = guidebook_collection.entry_class.new(:title => 'Bar', :price => 2.23, :category => category)
9
12
 
10
- if entry2.save
11
- puts "Entry saved"
13
+ if guidebook.save # save the guidebook with the associated category
14
+ puts "Guidebook Entry saved (#{guidebook[:@url]})"
12
15
  else
13
- puts "Entry could not be saved: #{entry2.errors.join(', ')}"
16
+ puts "Guidebook could not be saved: #{entry2.errors.join(', ')}"
14
17
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  require File.dirname(__FILE__) + '/authentication'
4
4
 
5
- collection = StorageRoom::Collection.find('4ddaf68b4d085d374a000003')
5
+ collection = StorageRoom::Collection.find('4dda7761b65245fde100005d')
6
6
 
7
7
  entry = collection.entries.resources.first
8
8
 
9
9
  entry.destroy
10
10
 
11
- puts "Destroyed #{entry[:title]}"
11
+ puts "Destroyed #{entry.title}"
@@ -6,6 +6,6 @@ collections = StorageRoom::Collection.all
6
6
 
7
7
  puts "Collections:"
8
8
 
9
- collections.resources.each do |collection|
10
- puts "- #{collection[:name]}"
9
+ collections.resources.each do |collection| # The array returned by Collection.all contains all the items in the resources key
10
+ puts "- #{collection.name}"
11
11
  end
@@ -8,14 +8,14 @@ lines = CSV.read(File.dirname(__FILE__) + '/guidebooks.csv') # Exported an Excel
8
8
 
9
9
  lines.slice!(0) # remove header line
10
10
 
11
- collection = StorageRoom::Collection.find('4ddaf68b4d085d374a000003')
12
- klass = collection.entry_class
11
+ collection = StorageRoom::Collection.find('4dda7761b65245fde100005d')
12
+ Guidebook = collection.entry_class
13
13
 
14
14
  lines.each do |row|
15
- guidebook = klass.new(:title => row[0], :price => row[1].to_f)
15
+ guidebook = Guidebook.new(:title => row[0], :price => row[1].to_f)
16
16
 
17
17
  if guidebook.save
18
- puts "Guidebook saved: #{guidebook[:title]}, #{guidebook[:price]}"
18
+ puts "Guidebook saved: #{guidebook.title}, #{guidebook.price}"
19
19
  else
20
20
  puts "Guidebook could not be saved: #{guidebook.errors.join(', ')}"
21
21
  end
@@ -2,12 +2,12 @@
2
2
 
3
3
  require File.dirname(__FILE__) + '/authentication'
4
4
 
5
- collection = StorageRoom::Collection.find('4ddaf68b4d085d374a000003')
5
+ collection = StorageRoom::Collection.find('4dda7761b65245fde100005d')
6
6
 
7
7
  entries = collection.entry_class.search(:title => 'Hitchhikers Guide to the Galaxy')
8
8
 
9
9
  puts "Entries with title 'Hitchhikers Guide to the Galaxy':"
10
10
 
11
11
  entries.resources.each do |entry|
12
- puts "- #{entry[:title]} : #{entry.url}"
12
+ puts "- #{entry.title} : #{entry[:@url]}"
13
13
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  require File.dirname(__FILE__) + '/authentication'
4
4
 
5
- collection = StorageRoom::Collection.find('4ddaf68b4d085d374a000003')
5
+ collection = StorageRoom::Collection.find('4dda7761b65245fde100005d')
6
6
 
7
7
  entry = collection.entries.resources.first
8
8
 
9
- entry[:title] = 'Foobar'
9
+ entry.title = 'Foobar'
10
10
 
11
11
  if entry.save
12
12
  puts "Entry saved"
data/lib/console.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'storage_room'
2
+
3
+ StorageRoom.debug = true
4
+
5
+ # Optionally have an init file in your home directory
6
+ file = File.expand_path('~/.storage_room_gem.rb')
7
+
8
+ begin
9
+ require file
10
+ rescue LoadError
11
+ StorageRoom.log("Could not load init file: #{file}")
12
+ end
data/lib/storage_room.rb CHANGED
@@ -5,38 +5,62 @@ begin; require 'rubygems'; rescue LoadError; end
5
5
 
6
6
  require 'httparty'
7
7
  require 'active_support/all'
8
+ require 'storage_room/extensions/const_defined'
8
9
 
10
+ require 'storage_room/plugins'
9
11
 
10
- module StorageRoom
11
- class AbstractMethod < RuntimeError; end
12
- class RequestFailed < RuntimeError; end
12
+ module StorageRoom
13
+ class AbstractMethod < RuntimeError; end
14
+ class RequestFailed < RuntimeError; end
15
+ class ResourceNotLoaded < RuntimeError; end
16
+ class ClassNotFound < RuntimeError; end
13
17
 
14
- autoload :Attributes, 'storage_room/attributes'
18
+ autoload :Plugins, 'storage_room/plugins'
19
+ autoload :Accessors, 'storage_room/accessors'
20
+ autoload :Proxy, 'storage_room/proxy'
21
+ autoload :IdentityMap, 'storage_room/identity_map'
15
22
 
16
- autoload :Base, 'storage_room/base'
17
- autoload :Model, 'storage_room/model'
18
- autoload :Array, 'storage_room/array'
19
- autoload :Field, 'storage_room/field'
20
- autoload :Embedded, 'storage_room/embedded'
23
+ autoload :Resource, 'storage_room/resource'
24
+ autoload :Model, 'storage_room/model'
25
+ autoload :Array, 'storage_room/array'
26
+
27
+ autoload :Embedded, 'storage_room/embedded'
28
+
29
+ autoload :Collection, 'storage_room/models/collection'
30
+ autoload :Entry, 'storage_room/models/entry'
31
+
32
+ autoload :Field, 'storage_room/embeddeds/field'
33
+
34
+ autoload :AtomicField, 'storage_room/embeddeds/fields/atomic_field'
35
+ autoload :StringField, 'storage_room/embeddeds/fields/atomic/string_field'
36
+ autoload :TimeField, 'storage_room/embeddeds/fields/atomic/time_field'
37
+ autoload :IntegerField, 'storage_room/embeddeds/fields/atomic/integer_field'
38
+ autoload :FloatField, 'storage_room/embeddeds/fields/atomic/float_field'
39
+ autoload :DateField, 'storage_room/embeddeds/fields/atomic/date_field'
40
+ autoload :BooleanField, 'storage_room/embeddeds/fields/atomic/boolean_field'
21
41
 
22
- autoload :Collection, 'storage_room/models/collection'
23
- autoload :Entry, 'storage_room/models/entry'
42
+ autoload :CompoundField, 'storage_room/embeddeds/fields/compound_field'
43
+ autoload :AttachmentField, 'storage_room/embeddeds/fields/compound/attachment_field'
44
+ autoload :FileField, 'storage_room/embeddeds/fields/compound/file_field'
45
+ autoload :ImageField, 'storage_room/embeddeds/fields/compound/image_field'
46
+ autoload :LocationField, 'storage_room/embeddeds/fields/compound/location_field'
24
47
 
25
- autoload :File, 'storage_room/embeddeds/file'
26
- autoload :Location, 'storage_room/embeddeds/location'
48
+ autoload :AssociationField, 'storage_room/embeddeds/fields/association_field'
49
+ autoload :OneAssociationField, 'storage_room/embeddeds/fields/associations/one_association_field'
50
+ autoload :ManyAssociationField, 'storage_room/embeddeds/fields/associations/many_association_field'
51
+
52
+ autoload :File, 'storage_room/embeddeds/file'
53
+ autoload :Image, 'storage_room/embeddeds/image'
54
+ autoload :Location, 'storage_room/embeddeds/location'
27
55
 
28
56
 
29
57
  class << self
30
- attr_reader :api_key
31
- attr_reader :user_agent
32
- attr_reader :account_id
33
- attr_reader :ssl
34
- attr_reader :proxy_server
35
- attr_reader :proxy_port
58
+ attr_reader :api_key, :user_agent, :account_id, :ssl, :proxy_server, :proxy_port
59
+ attr_accessor :debug
36
60
 
37
61
  # Authenticate once before making any requests with your account id and the application's api key
38
62
  def authenticate(account_id, api_key)
39
- Base.basic_auth(api_key, 'X')
63
+ Resource.basic_auth(api_key, 'X')
40
64
  @api_key = api_key
41
65
  @account_id = account_id
42
66
  update_uri
@@ -44,7 +68,7 @@ module StorageRoom
44
68
 
45
69
  # Change the user agent that is sent with all requests
46
70
  def user_agent=(agent)
47
- Base.headers.merge!('User-Agent' => agent)
71
+ Resource.headers.merge!('User-Agent' => agent)
48
72
  @user_agent = agent
49
73
  end
50
74
 
@@ -58,6 +82,20 @@ module StorageRoom
58
82
  @server || 'api.storageroomapp.com'
59
83
  end
60
84
 
85
+ # Hash of all mappings from an Entry's @type to a local Ruby class, if a mapping doesn't exist a class name will be created automatically
86
+ def entry_class_mappings
87
+ @entry_class_mappings ||= {}
88
+ end
89
+
90
+ # Add a new mapping
91
+ def add_entry_class_mapping(name, class_name)
92
+ self.entry_class_mappings[name] = class_name.to_s
93
+ end
94
+
95
+ def entry_class_for_name(name) #:nodoc:
96
+ self.entry_class_mappings[name] || name.gsub(/\s+/, "").classify
97
+ end
98
+
61
99
  # Requests are made with SSL
62
100
  def ssl=(ssl)
63
101
  @ssl = ssl
@@ -68,32 +106,33 @@ module StorageRoom
68
106
  def http_proxy(server, port)
69
107
  @proxy_server = server
70
108
  @proxy_port = port
71
- Base.http_proxy(server, port)
109
+ Resource.http_proxy(server, port)
72
110
  end
73
111
 
74
112
  # Return a Ruby class for a StorageRoom type
75
- def class_for_name(name)
76
- is_ruby_19 = method(:const_defined?).arity == 1 # ruby 1.9 check
77
-
78
- if is_ruby_19 ? StorageRoom.const_defined?(name) : StorageRoom.const_defined?(name, false)
113
+ def class_for_name(name)
114
+ name_with_mapping = entry_class_for_name(name)
115
+
116
+ if StorageRoom.is_constant_defined?(name)
79
117
  "StorageRoom::#{name}".constantize
80
- elsif is_ruby_19 ? Object.const_defined?(name) : Object.const_defined?(name, false)
81
- name.constantize
118
+ elsif Object.is_constant_defined?(name_with_mapping)
119
+ name_with_mapping.constantize
82
120
  else
83
- klass = Class.new(Entry)
84
- Object.const_set(name, klass)
85
-
86
- klass
121
+ raise ClassNotFound.new("Unknown class: #{name}")
87
122
  end
88
123
  end
89
124
 
125
+ def log(msg) #:nodoc:
126
+ puts("[DEBUG] #{msg}") if debug
127
+ end
128
+
90
129
  private
91
130
 
92
131
  def update_uri
93
132
  protocol = self.ssl == true ? 'https' : 'http'
94
133
 
95
- Base.base_uri "#{protocol}://#{self.server}/accounts/#{self.account_id}"
134
+ url = Resource.base_uri("#{protocol}://#{self.server}/accounts/#{self.account_id}")
96
135
  end
97
136
 
98
137
  end
99
- end
138
+ end
@@ -0,0 +1,190 @@
1
+ module StorageRoom
2
+
3
+ # Module that contains attributes methods shared between StorageRoom::Resource and StorageRoom::Embedded
4
+ module Accessors
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ self.class_inheritable_accessor :attribute_options
9
+ self.attribute_options ||= {}
10
+ end
11
+
12
+ module InstanceMethods
13
+ # Optionally pass attributes to set up the object
14
+ def initialize(hash={})
15
+ self.attributes = hash
16
+ end
17
+
18
+ # Shortcut to get an attribute.
19
+ def [](name)
20
+ self.response_data[name]
21
+ end
22
+
23
+ # Takes a response data hash, saves it in the record and initializes the class from the response data
24
+ def set_from_response_data(hash)
25
+ self.response_data = hash
26
+ self.initialize_from_response_data
27
+ self
28
+ end
29
+
30
+ # Return all of the objects attributes
31
+ def response_data
32
+ @_response_data ||= Hash.new.with_indifferent_access
33
+ end
34
+
35
+ # Set the objects attributes with a hash. Only attributes passed in the hash are changed, existing ones are not overridden.
36
+ def response_data=(hash = {})
37
+ response_data.merge!(hash)
38
+ end
39
+
40
+ # The attributes as they were defined with key, one, many
41
+ def attributes
42
+ @_attributes ||= Hash.new.with_indifferent_access
43
+ end
44
+
45
+ def attributes=(args = {})
46
+ attributes.merge!(args)
47
+ end
48
+
49
+ def as_json(args = {}) # :nodoc:
50
+ to_hash(args)
51
+ end
52
+
53
+ # ActiveSupport seemed to cause problems when just using as_json, so using to_hash
54
+ def to_hash(args = {}) # :nodoc:
55
+ args ||= {}
56
+ hash = {}
57
+
58
+ self.attributes.each do |name, value|
59
+ hash[name] = if value.is_a?(::Array)
60
+ value.map{|x| x.respond_to?(:to_hash) ? x.to_hash(:nested => true) : x }
61
+ elsif value.respond_to?(:to_hash)
62
+ value.to_hash(:nested => true)
63
+ elsif value.respond_to?(:as_json)
64
+ value.as_json(:nested => true)
65
+ else
66
+ value
67
+ end
68
+ end
69
+
70
+ hash
71
+ end
72
+
73
+ def inspect # :nodoc:
74
+ body = attributes.map{|k, v| "#{k}: #{attribute_for_inspect(v)}"}.join(', ')
75
+ "#<#{self.class} #{body}>"
76
+ end
77
+
78
+ # Reset an object to its initial state with all attributes unset
79
+ def reset!
80
+ @_response_data = Hash.new.with_indifferent_access
81
+ @_attributes = Hash.new.with_indifferent_access
82
+ true
83
+ end
84
+
85
+ # Has a Resource been loaded from the API
86
+ def loaded?
87
+ self.response_data.present?
88
+ end
89
+
90
+ def proxy? # :nodoc:
91
+ false
92
+ end
93
+
94
+ protected
95
+ # Iterate over the response data and initialize the attributes
96
+ def initialize_from_response_data # :nodoc:
97
+ self.class.attribute_options.each do |name, options|
98
+ value = if options[:type] == :key
99
+ self[name].blank? ? options[:default] : self[name]
100
+ elsif options[:type] == :one
101
+ hash = self[name.to_s]
102
+ hash && hash['@type'] ? self.class.new_from_response_data(hash) : nil
103
+ elsif options[:type] == :many
104
+ response_data[name] && response_data[name].map{|hash| self.class.new_from_response_data(hash)} || []
105
+ else
106
+ raise "Invalid type: #{options[:type]}"
107
+ end
108
+
109
+ send("#{name}=", value)
110
+ end
111
+ end
112
+
113
+ def ensure_loaded # :nodoc:
114
+ if loaded?
115
+ yield if block_given?
116
+ else
117
+ raise StorageRoom::ResourceNotLoaded
118
+ end
119
+ end
120
+
121
+ def attribute_for_inspect(value) # :nodoc:
122
+ if value.is_a?(String) && value.length > 50
123
+ "#{value[0..50]}...".inspect
124
+ elsif value.is_a?(Date) || value.is_a?(Time)
125
+ %("#{value.iso8601}")
126
+ else
127
+ value.inspect
128
+ end
129
+ end
130
+ end
131
+
132
+ module ClassMethods
133
+ # Load an object with the specified URL from the API
134
+ def load(url, parameters = {})
135
+ return nil if url.blank?
136
+
137
+ StorageRoom.log("Loading #{url}")
138
+ httparty = get(url, parameters)
139
+
140
+ handle_critical_response_errors(httparty)
141
+ new_from_response_data(httparty.parsed_response.first[1])
142
+ end
143
+
144
+ # Creates a new object of the correct class and initializes it from the response data
145
+ def new_from_response_data(response_data)
146
+ object = StorageRoom.class_for_name(response_data['@type']).new.set_from_response_data(response_data)
147
+
148
+ if object.is_a?(Entry) && !object.loaded? && !object.proxy?
149
+ StorageRoom.log("Return #{response_data['url']} proxied")
150
+ Proxy.new(object)
151
+ else
152
+ object
153
+ end
154
+ end
155
+
156
+ # Defines a basic key for a Resource
157
+ def key(name, options = {})
158
+ options.merge!(:type => :key, :default => nil)
159
+ define_attribute_methods(name, options)
160
+ end
161
+
162
+ # Defines a to-one association for a Resource (embedded or association)
163
+ def one(name, options = {})
164
+ options.merge!(:type => :one, :default => nil)
165
+ define_attribute_methods(name, options)
166
+ end
167
+
168
+ # Defines a to-many association for a Resource (embedded or association)
169
+ def many(name, options = {})
170
+ options.merge!(:type => :many, :default => [])
171
+ define_attribute_methods(name, options)
172
+ end
173
+
174
+ # Creates getter and setter for an attribute
175
+ def define_attribute_methods(name, options) # :nodoc:
176
+ define_method name do
177
+ attributes[name] || options[:default]
178
+ end
179
+
180
+ define_method "#{name}=" do |object|
181
+ attributes[name] = object
182
+ end
183
+
184
+ self.attribute_options[name] = options
185
+ end
186
+ end
187
+
188
+ end
189
+
190
+ end