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