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,28 +1,8 @@
1
1
  module StorageRoom
2
2
  # A container object that contains many models (collections or entries)
3
- class Array < Base
4
- attr_accessor :resources
5
-
6
-
7
- def initialize(attributes = {})
8
- self.resources = []
9
- super
10
- end
11
-
12
- # Set the array with the attributes from the API
13
- def set_from_api(attributes)
14
- super(attributes)
15
-
16
- self.resources = attributes['resources'].map{|item| self.class.create_from_api(item)} # transform hashes to real objects
17
- attributes.delete('resources')
18
- end
19
-
20
- # Reset the Array to its default state with all attributes unset
21
- def reset!
22
- super
23
- @resources = []
24
- end
25
-
3
+ class Array < Resource
4
+ many :resources
5
+
26
6
  # Replaces the objects content with the next page of the array if a next page exists
27
7
  def load_next_page!
28
8
  if self[:@next_page_url].present?
@@ -1,7 +1,7 @@
1
1
  module StorageRoom
2
2
  # An embedded object that is not only a simple type (like an integer, string)
3
3
  class Embedded
4
- include Attributes
4
+ include Accessors
5
5
 
6
6
  end
7
7
  end
@@ -0,0 +1,28 @@
1
+ module StorageRoom
2
+
3
+ class Field < Embedded
4
+ key :name
5
+ key :identifier
6
+
7
+ key :hint
8
+
9
+ key :input_type
10
+
11
+ key :required
12
+ key :unique
13
+
14
+ key :maximum_length
15
+ key :minimum_length
16
+
17
+ key :minimum_number
18
+ key :maximum_number
19
+
20
+ key :minimum_size
21
+ key :maximum_size
22
+
23
+ def add_to_entry_class(klass) # :nodoc:
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module StorageRoom
2
+
3
+ class AssociationField < Field
4
+ key :collection_url
5
+
6
+ def add_to_entry_class(klass) # :nodoc:
7
+ super
8
+
9
+ collection
10
+ end
11
+
12
+ # The target collection of the association field
13
+ def collection
14
+ @collection ||= Collection.load(self.collection_url)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module StorageRoom
2
+
3
+ class ManyAssociationField < AssociationField
4
+ def add_to_entry_class(klass) # :nodoc:
5
+ super
6
+ klass.send(:many, identifier)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module StorageRoom
2
+
3
+ class OneAssociationField < AssociationField
4
+ def add_to_entry_class(klass) # :nodoc:
5
+ super
6
+ klass.send(:one, identifier)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class BooleanField < AtomicField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class DateField < AtomicField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class FloatField < AtomicField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class IntegerField < AtomicField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class StringField < AtomicField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class TimeField < AtomicField
4
+
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ module StorageRoom
2
+
3
+ class AtomicField < Field
4
+ key :default_value
5
+
6
+ key :choices
7
+ key :include_blank_choice
8
+
9
+ def add_to_entry_class(klass) # :nodoc:
10
+ super
11
+ klass.send(:key, identifier)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class AttachmentField < CompoundField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class FileField < CompoundField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class ImageField < CompoundField
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+
3
+ class LocationField < CompoundField
4
+
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module StorageRoom
2
+
3
+ class CompoundField < Field
4
+ def add_to_entry_class(klass) # :nodoc:
5
+ super
6
+ klass.send(:one, identifier)
7
+ end
8
+
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  module StorageRoom
2
- # A file that is embedded in a entry
2
+ # Any file
3
3
  class File < Embedded
4
4
 
5
5
  end
@@ -0,0 +1,6 @@
1
+ module StorageRoom
2
+ # An Image with optional thumbnails
3
+ class Image < Embedded
4
+
5
+ end
6
+ end
@@ -1,6 +1,7 @@
1
1
  module StorageRoom
2
2
  # A location with coordinates that is embedded in a entry
3
3
  class Location < Embedded
4
-
4
+ key :lat
5
+ key :lng
5
6
  end
6
7
  end
@@ -0,0 +1,12 @@
1
+ module ConstDefinedExtension
2
+ def is_constant_defined?(const)
3
+ if ::RUBY_VERSION =~ /1.9/
4
+ const_defined?(const, false)
5
+ else
6
+ const_defined?(const)
7
+ end
8
+ end
9
+ end
10
+
11
+ Object.send(:include, ConstDefinedExtension)
12
+ Module.send(:include, ConstDefinedExtension)
@@ -0,0 +1,117 @@
1
+ module StorageRoom
2
+ # With inspiration from John Nunemaker's MongoMapper
3
+ module IdentityMap
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ IdentityMap.models << self
8
+ end
9
+
10
+ def self.models
11
+ @models ||= Set.new
12
+ end
13
+
14
+ def self.clear
15
+ models.each { |m| m.identity_map.clear }
16
+ end
17
+
18
+ module ClassMethods
19
+ def inherited(descendant)
20
+ descendant.identity_map = identity_map
21
+ super
22
+ end
23
+
24
+ # Load an object from the identity map if the key (URL) exists
25
+ def load(url, parameters = {})
26
+ return nil if url.nil?
27
+
28
+ if identity_map_on? && object = identity_map[url]
29
+ StorageRoom.log("Loaded #{object} from identity map (load)")
30
+ object
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ # Load an object from the identity map or create it
37
+ def new_from_response_data(response_data)
38
+ url = response_data['@url'] || response_data['url']
39
+ object = url ? identity_map[url] : nil
40
+
41
+ if object.present? && identity_map_on?
42
+ StorageRoom.log("Loaded #{object} from identity map (new_from_response_data)")
43
+ object.set_from_response_data(response_data)
44
+ else
45
+ object = super
46
+ if identity_map_on? && url.present? && object.is_a?(Model)
47
+ identity_map[url] = object
48
+ StorageRoom.log("Storing #{url} in identity map: #{object}")
49
+ end
50
+ end
51
+
52
+ object
53
+ end
54
+
55
+ def identity_map
56
+ @identity_map ||= {}
57
+ end
58
+
59
+ def identity_map=(v)
60
+ @identity_map = v
61
+ end
62
+
63
+ def identity_map_status
64
+ defined?(@identity_map_status) ? @identity_map_status : true
65
+ end
66
+
67
+ def identity_map_on
68
+ @identity_map_status = true
69
+ end
70
+
71
+ def identity_map_off
72
+ @identity_map_status = false
73
+ end
74
+
75
+ def identity_map_on?
76
+ identity_map_status
77
+ end
78
+
79
+ def identity_map_off?
80
+ !identity_map_on?
81
+ end
82
+
83
+ def without_identity_map(&block)
84
+ identity_map_off
85
+ yield
86
+ ensure
87
+ identity_map_on
88
+ end
89
+
90
+ end
91
+
92
+ module InstanceMethods
93
+ def identity_map
94
+ self.class.identity_map
95
+ end
96
+
97
+ def in_identity_map?
98
+ return false if self[:@url].blank?
99
+ identity_map.include?(self[:@url])
100
+ end
101
+
102
+ def create(*args)
103
+ if result = super
104
+ identity_map[self[:@url]] = self if self.class.identity_map_on?
105
+ end
106
+ result
107
+ end
108
+
109
+ def destroy
110
+ identity_map.delete(self[:@url]) if self.class.identity_map_on?
111
+ super
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+
@@ -1,6 +1,6 @@
1
1
  module StorageRoom
2
2
  # Abstract superclass for classes that can persist to the remote servers
3
- class Model < Base
3
+ class Model < Resource
4
4
  class << self
5
5
  # Create a new model with the passed attributes
6
6
  def create(attributes={})
@@ -35,31 +35,21 @@ module StorageRoom
35
35
 
36
36
  # Create a new model and set its attributes
37
37
  def initialize(attributes={})
38
- @new_record = true
39
38
  @errors = []
40
39
 
41
40
  super
42
41
  end
43
-
44
- # Set the attributes of the model from the API
45
- def set_from_api(attributes)
46
- super
47
- @new_record = false
48
-
49
- self.attributes
50
- end
51
-
42
+
52
43
  # Reset the model to its default state
53
44
  def reset!
54
45
  super
55
- @new_record = true
56
46
  @errors = []
57
47
  true
58
48
  end
59
49
 
60
50
  # Has this model been saved to the API already?
61
51
  def new_record?
62
- @new_record ? true : false
52
+ self['@version'] ? false : true
63
53
  end
64
54
 
65
55
  # Create a new model or update an existing model on the server
@@ -77,7 +67,7 @@ module StorageRoom
77
67
  # Update an existing model on the server
78
68
  def update
79
69
  return false if new_record?
80
- httparty = self.class.put(url, :body => to_json)
70
+ httparty = self.class.put(self[:@url], :body => to_json)
81
71
  handle_save_response(httparty)
82
72
  end
83
73
 
@@ -85,7 +75,7 @@ module StorageRoom
85
75
  def destroy
86
76
  return false if new_record?
87
77
 
88
- httparty = self.class.delete(url)
78
+ httparty = self.class.delete(self[:@url])
89
79
  self.class.handle_critical_response_errors(httparty)
90
80
 
91
81
  true
@@ -96,8 +86,17 @@ module StorageRoom
96
86
  self.errors.empty?
97
87
  end
98
88
 
99
- def as_json(args = {}) # :nodoc:
100
- {self.class.json_name => self.attributes.reject{|k, v| self.class.meta_data?(k) && k.to_s != '@version'}}
89
+ # ActiveSupport caused problems when using as_json, so using to_hash
90
+ def to_hash(args = {}) # :nodoc:
91
+ args ||= {}
92
+
93
+ if args[:nested]
94
+ {'url' => self[:@url] || self[:url]}
95
+ else
96
+ hash = super
97
+ hash.merge!('@version' => self['@version']) unless new_record?
98
+ {self.class.json_name => hash}
99
+ end
101
100
  end
102
101
 
103
102
  # The validation errors that were returned by the server
@@ -105,22 +104,19 @@ module StorageRoom
105
104
  @errors ||= []
106
105
  end
107
106
 
108
- # ===================
109
- # = Private Methods =
110
- # ===================
111
-
112
- private
107
+ protected
113
108
  def handle_save_response(httparty) # :nodoc:
114
109
  self.class.handle_critical_response_errors(httparty)
115
110
 
116
111
  if httparty.response.code == '200' || httparty.response.code == '201'
117
- self.set_from_api(httparty.parsed_response.first[1])
112
+ self.set_from_response_data(httparty.parsed_response.first[1])
118
113
  true
119
114
  else
120
115
  @errors = httparty.parsed_response['error']['message']
121
116
  false
122
117
  end
123
118
  end
119
+
124
120
 
125
121
  end
126
122
  end