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.
- 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
|