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
@@ -1,34 +1,99 @@
1
1
  module StorageRoom
2
2
  # A collection is used to define the structure of a data set.
3
- class Collection < Model
3
+ class Collection < Model
4
+ key :name
5
+ key :primary_field_identifier
6
+
7
+ many :fields
8
+
4
9
  class << self
5
10
  def index_path # :nodoc:
6
- '/collections'
11
+ "#{Resource.base_uri}/collections"
7
12
  end
8
13
 
9
14
  def show_path(collection_id) # :nodoc:
10
15
  "#{index_path}/#{collection_id}"
11
16
  end
12
-
13
- def entries_path(collection_id) # :nodoc:
14
- "#{show_path(collection_id)}/entries"
15
- end
16
-
17
+
17
18
  def json_name # :nodoc:
18
19
  'collection'
19
20
  end
20
21
  end
21
22
 
23
+ # The class name of the collection's entries, can be overridden with a mapping
24
+ def entry_class_name
25
+ ensure_loaded { StorageRoom.entry_class_for_name(name)}
26
+ end
27
+
28
+ # The class for the collection's entries
29
+ def entry_class
30
+ require_initialized_entry_class do
31
+ self.entry_class_name.constantize
32
+ end
33
+ end
34
+
22
35
  # Load all the entries of a collection
23
36
  def entries
24
- Array.load(self[:@entries_url])
37
+ require_initialized_entry_class do
38
+ Array.load(self[:@entries_url])
39
+ end
25
40
  end
26
41
 
27
- # The class of the collection's objects
28
- def entry_class
29
- StorageRoom::Entry.class_with_options(self[:name].classify, :collection_path => self.url)
42
+ # The field with a specific identifier
43
+ def field(identifier)
44
+ ensure_loaded do
45
+ fields.each do |f|
46
+ return f if f.identifier == identifier
47
+ end
48
+ end
49
+
50
+ nil
30
51
  end
52
+
53
+ protected
54
+ def initialize_from_response_data
55
+ super
56
+ require_initialized_entry_class
57
+ end
58
+
59
+ def initialize_entry_class
60
+ name = self.entry_class_name
61
+
62
+ klass = if Object.is_constant_defined?(name.to_sym)
63
+ name.constantize
64
+ else
65
+ klass = Class.new(Entry)
66
+ Object.const_set(name, klass)
67
+ klass
68
+ end
31
69
 
70
+ klass.collection = self
71
+
72
+ fields.each do |field|
73
+ field.add_to_entry_class(klass)
74
+ end
75
+
76
+ true
77
+ end
32
78
 
79
+ def deconstruct_entry_class
80
+ if Object.is_const_defined?(self.entry_class_name.to_sym)
81
+ Object.send(:remove_const, self.entry_class_name)
82
+ end
83
+
84
+ true
85
+ end
86
+
87
+ def recreate_entry_class
88
+ self.deconstruct_entry_class
89
+ self.initialize_entry_class
90
+ end
91
+
92
+ def require_initialized_entry_class
93
+ ensure_loaded do
94
+ self.initialize_entry_class
95
+ yield if block_given?
96
+ end
97
+ end
33
98
  end
34
99
  end
@@ -1,20 +1,10 @@
1
1
  module StorageRoom
2
2
  class Entry < Model
3
- class_inheritable_accessor :collection_path
4
-
5
- class << self
6
- def class_with_options(name, options = {})
7
- # TODO_SK: check options
8
-
9
- klass = StorageRoom.class_for_name(name)
10
-
11
- klass.collection_path = options[:collection_path]
3
+ class_inheritable_accessor :collection
12
4
 
13
- klass
14
- end
15
-
16
- def index_path
17
- "#{collection_path}/entries"
5
+ class << self
6
+ def index_path # :nodoc:
7
+ "#{collection[:@url]}/entries"
18
8
  end
19
9
 
20
10
  def show_path(entry_id) # :nodoc:
@@ -35,26 +25,10 @@ module StorageRoom
35
25
  end
36
26
  end
37
27
 
38
- # Sets a entry with a hash from the API.
39
- def set_from_api(attributes)
40
- super(attributes)
41
-
42
- self.attributes.each do |k, v|
43
- if v.is_a?(Hash) && v[:@type].present?
44
- object = StorageRoom.class_for_name(v[:@type]).new
45
- object.set_from_api(v)
46
- self.attributes[k] = object
47
- end
48
- end
49
-
50
- self.attributes
51
- end
52
-
53
28
  # The collection of a entry
54
29
  def collection
55
- Collection.load(self[:@collection_url] || self.class.collection_path)
56
- end
57
-
30
+ self.class.collection
31
+ end
58
32
 
59
33
  end
60
34
  end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module StorageRoom
4
+ module Plugins
5
+ include ActiveSupport::DescendantsTracker
6
+
7
+ def plugins
8
+ @plugins ||= []
9
+ end
10
+
11
+ def plugin(mod)
12
+ include mod
13
+ direct_descendants.each {|model| model.send(:include, mod) }
14
+ plugins << mod
15
+ end
16
+
17
+ def included(base = nil, &block)
18
+ direct_descendants << base if base
19
+ super
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,49 @@
1
+ module StorageRoom
2
+ # The Proxy class is used in resource associations to delay loading of a resource for as long as possible.
3
+ # Some method calls are directly forwarded to the resource, for others the resource is loaded first from the webservice
4
+ # and afterwards the call is made.
5
+ # The Proxy tries to be as transparent as possible so that you basically never know that you have a proxy object instead of a real resource.
6
+ class Proxy
7
+ METHODS_WITHOUT_RELOAD = [:reload, :response_data, :loaded?, :class, :set_from_response_data, :present?, :is_a?, :respond_to?, :as_json, :to_hash] # TODO: figure out what other methods might be needed?
8
+
9
+ instance_methods.each { |m| undef_method m unless m =~ /^(__|instance_eval|object_id)/ } # http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
10
+
11
+ def initialize(object, parameters={})
12
+ @object = object
13
+ @parameters = parameters
14
+ end
15
+
16
+ def proxy?
17
+ true
18
+ end
19
+
20
+ def inspect
21
+ if @object.loaded?
22
+ @object.inspect
23
+ else
24
+ "#<#{@object.class} (proxied)>"
25
+ end
26
+ end
27
+
28
+ def to_s
29
+ inspect
30
+ end
31
+
32
+ def _object
33
+ @object
34
+ end
35
+
36
+ # Forward all method calls to the proxied object, reload if necessary
37
+ def method_missing(method_name, *args, &block)
38
+ if @object.loaded? || METHODS_WITHOUT_RELOAD.include?(method_name)
39
+ # no need to reload
40
+ else
41
+ StorageRoom.log("Reloading #{@object['url']} due to #{method_name}")
42
+ @object.reload(@object['url'], @parameters)
43
+ end
44
+
45
+ @object.send(method_name, *args, &block)
46
+ end
47
+ end
48
+
49
+ end
@@ -1,22 +1,17 @@
1
1
  module StorageRoom
2
2
  # The superclass of all the classes that load data from the API
3
- class Base
4
- include Attributes
3
+ class Resource
5
4
  include HTTParty
5
+ extend Plugins
6
+
7
+ include Accessors
8
+
9
+ plugin IdentityMap
6
10
 
7
11
  headers 'User-Agent' => 'StorageRoom Ruby Gem', 'Accept' => 'application/json', 'Content-Type' => 'application/json'
8
12
  format :json
9
13
 
10
-
11
- class << self
12
- # Load an object with the specified URL from the API
13
- def load(url, parameters = {})
14
- httparty = get(url, parameters)
15
-
16
- handle_critical_response_errors(httparty)
17
- create_from_api(httparty.parsed_response.first[1])
18
- end
19
-
14
+ class << self
20
15
  # Handle known server errors
21
16
  def handle_critical_response_errors(httparty) # :nodoc:
22
17
  case httparty.response.code
@@ -26,21 +21,6 @@ module StorageRoom
26
21
  end
27
22
  end
28
23
 
29
- # Creates a new object with a hash from the API with a @type attribute
30
- def create_from_api(hash) # :nodoc:
31
- type = hash['@type']
32
-
33
- object = case type
34
- when 'Array' then Array.new
35
- when 'Collection' then Collection.new
36
- else # entry
37
- StorageRoom.class_for_name(type.classify).new
38
- end
39
-
40
- object.set_from_api(hash)
41
- object
42
- end
43
-
44
24
  # Find out if a key is user-defined or meta-data (begins with @)
45
25
  def meta_data?(key)
46
26
  key[0...1] == '@'
@@ -50,18 +30,15 @@ module StorageRoom
50
30
  # Reload an object from the API. Optionally pass an URL.
51
31
  def reload(url = nil, parameters = {})
52
32
  httparty = self.class.get(url || self[:@url], parameters)
53
- set_from_api(httparty.parsed_response.first[1])
33
+ set_from_response_data(httparty.parsed_response.first[1])
54
34
  true
55
35
  end
56
-
57
- # Returns the remote URL of the object
58
- def url
59
- self[:@url]
60
- end
61
36
 
62
- def version
63
- self[:@version]
37
+ # Has the Resource been loaded from the API?
38
+ def loaded?
39
+ self['@url'] ? true : false
64
40
  end
65
-
41
+
42
+
66
43
  end
67
44
  end
@@ -15,22 +15,6 @@
15
15
  "hint": "",
16
16
  "choices": [],
17
17
  "identifier": "title"
18
- },
19
- {
20
- "name": "PDF",
21
- "required": false,
22
- "input_type": "file",
23
- "@type": "FileField",
24
- "hint": "Upload a PDF file",
25
- "identifier": "pdf"
26
- },
27
- {
28
- "name": "location",
29
- "required": false,
30
- "input_type": "location",
31
- "@type": "LocationField",
32
- "hint": "",
33
- "identifier": "location"
34
18
  }],
35
19
  "@type": "Collection",
36
20
  "@url": "http://api.storageroomapp.com/accounts/4c8fd48542507175aa00002f/collections/4ddaf68b4d085d374a000003"
data/spec/spec_helper.rb CHANGED
@@ -10,6 +10,7 @@ RSpec.configure do |config|
10
10
 
11
11
  config.before(:each) do
12
12
  StorageRoom.authenticate('USER_ID', 'APPLICATION_API_KEY')
13
+ StorageRoom::IdentityMap.clear
13
14
  end
14
15
  end
15
16
 
@@ -0,0 +1,107 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ module StorageRoom
4
+ class TestAccessors
5
+ include Accessors
6
+
7
+ key :test
8
+ key :test2
9
+ end
10
+ end
11
+
12
+ describe StorageRoom::TestAccessors do
13
+
14
+ context "Instance" do
15
+ before(:each) do
16
+ @test = StorageRoom::TestAccessors.new(:test => 1)
17
+ end
18
+
19
+ describe "#initialize" do
20
+ it "should set attributes" do
21
+ @test.test.should == 1
22
+ @test['test'].should be_nil
23
+ @test.test2.should be_nil
24
+ @test['test2'].should be_nil
25
+ end
26
+ end
27
+
28
+ describe "#set_from_response_data" do
29
+ before(:each) do
30
+ @test.set_from_response_data(:test2 => 3)
31
+ end
32
+
33
+ it "should reset attributes" do
34
+ @test.test.should be_nil
35
+ end
36
+
37
+ it "should set new attributes" do
38
+ @test['test2'].should == 3
39
+ @test.test2.should == 3
40
+ end
41
+ end
42
+
43
+ describe "#inspect" do
44
+ it "should output string" do
45
+ @test.inspect.should be_present
46
+ end
47
+ end
48
+
49
+ describe "#[]" do
50
+ it "should get attribute" do
51
+ @test.set_from_response_data(:test2 => 3)
52
+
53
+ @test[:test].should be_nil
54
+ @test[:test2].should == 3
55
+ end
56
+ end
57
+
58
+ describe "#attributes" do
59
+ it "should return existing attributes" do
60
+ @test.attributes[:test].should == 1
61
+ @test.attributes['test'].should == 1
62
+ @test.attributes['test2'].should be_nil
63
+ end
64
+ end
65
+
66
+ describe "#attributes=" do
67
+ before(:each) do
68
+ @test.attributes = {:test => 0}
69
+ end
70
+
71
+ it "should set attributes" do
72
+ @test.attributes[:test].should == 0
73
+ end
74
+ end
75
+
76
+ describe "#as_json" do
77
+ it "should return attributes as hash" do
78
+ hash = @test.as_json
79
+ hash.should be_an_instance_of(Hash)
80
+ hash['test'].should == 1
81
+ end
82
+ end
83
+
84
+ describe "#reset!" do
85
+ it "should reset" do
86
+ @test.response_data = {'test' => 1}
87
+ @test.reset!
88
+ @test.attributes.should == {}
89
+ @test.response_data.should == {}
90
+ end
91
+ end
92
+
93
+ describe "#loaded?" do
94
+ it "should return true" do
95
+ @test.response_data = {'test' => 1}
96
+ @test.should be_loaded
97
+ end
98
+
99
+ it "should return false" do
100
+ @test.reset!
101
+ @test.should_not be_loaded
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ end