storage_room 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/README.rdoc +10 -4
- data/Rakefile +3 -1
- data/TODO +7 -2
- data/VERSION +1 -1
- data/examples/authentication.rb +5 -3
- data/examples/create_entry.rb +8 -5
- data/examples/destroy_entry.rb +2 -2
- data/examples/get_collections.rb +2 -2
- data/examples/import_csv.rb +4 -4
- data/examples/search_entries.rb +2 -2
- data/examples/update_entry.rb +2 -2
- data/lib/console.rb +12 -0
- data/lib/storage_room.rb +73 -34
- data/lib/storage_room/accessors.rb +190 -0
- data/lib/storage_room/array.rb +3 -23
- data/lib/storage_room/embedded.rb +1 -1
- data/lib/storage_room/embeddeds/field.rb +28 -0
- data/lib/storage_room/embeddeds/fields/association_field.rb +17 -0
- data/lib/storage_room/embeddeds/fields/associations/many_association_field.rb +9 -0
- data/lib/storage_room/embeddeds/fields/associations/one_association_field.rb +9 -0
- data/lib/storage_room/embeddeds/fields/atomic/boolean_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/atomic/date_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/atomic/float_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/atomic/integer_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/atomic/string_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/atomic/time_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/atomic_field.rb +15 -0
- data/lib/storage_room/embeddeds/fields/compound/attachment_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/compound/file_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/compound/image_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/compound/location_field.rb +6 -0
- data/lib/storage_room/embeddeds/fields/compound_field.rb +10 -0
- data/lib/storage_room/embeddeds/file.rb +1 -1
- data/lib/storage_room/embeddeds/image.rb +6 -0
- data/lib/storage_room/embeddeds/location.rb +2 -1
- data/lib/storage_room/extensions/const_defined.rb +12 -0
- data/lib/storage_room/identity_map.rb +117 -0
- data/lib/storage_room/model.rb +19 -23
- data/lib/storage_room/models/collection.rb +76 -11
- data/lib/storage_room/models/entry.rb +6 -32
- data/lib/storage_room/plugins.rb +22 -0
- data/lib/storage_room/proxy.rb +49 -0
- data/lib/storage_room/{base.rb → resource.rb} +13 -36
- data/spec/fixtures/collection.json +0 -16
- data/spec/spec_helper.rb +1 -0
- data/spec/storage_room/accessors_spec.rb +107 -0
- data/spec/storage_room/array_spec.rb +13 -9
- data/spec/storage_room/embedded_spec.rb +5 -1
- data/spec/storage_room/embeddeds/field_spec.rb +25 -0
- data/spec/storage_room/embeddeds/fields/association_field_spec.rb +29 -0
- data/spec/storage_room/embeddeds/fields/associations/many_association_field_spec.rb +21 -0
- data/spec/storage_room/embeddeds/fields/associations/one_association_field_spec.rb +19 -0
- data/spec/storage_room/embeddeds/fields/atomic/boolean_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/atomic/date_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/atomic/float_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/atomic/integer_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/atomic/string_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/atomic/time_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/atomic_field_spec.rb +27 -0
- data/spec/storage_room/embeddeds/fields/compound/attachment_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/compound/file_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/compound/image_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/compound/location_field_spec.rb +5 -0
- data/spec/storage_room/embeddeds/fields/compound_field_spec.rb +17 -0
- data/spec/storage_room/embeddeds/location_spec.rb +13 -1
- data/spec/storage_room/identity_map_spec.rb +53 -0
- data/spec/storage_room/model_spec.rb +70 -50
- data/spec/storage_room/models/collection_spec.rb +57 -14
- data/spec/storage_room/models/entry_spec.rb +16 -20
- data/spec/storage_room/proxy_spec.rb +58 -0
- data/spec/storage_room/resource_spec.rb +98 -0
- data/spec/storage_room_spec.rb +45 -9
- data/storage_room.gemspec +48 -9
- data/tasks/storage_room.rake +3 -0
- metadata +49 -10
- data/lib/storage_room/attributes.rb +0 -46
- data/lib/storage_room/field.rb +0 -8
- data/spec/storage_room/attributes_spec.rb +0 -82
- data/spec/storage_room/base_spec.rb +0 -118
- 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
|
-
|
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
|
-
|
37
|
+
require_initialized_entry_class do
|
38
|
+
Array.load(self[:@entries_url])
|
39
|
+
end
|
25
40
|
end
|
26
41
|
|
27
|
-
# The
|
28
|
-
def
|
29
|
-
|
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 :
|
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
|
-
|
14
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
63
|
-
|
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
@@ -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
|