synamoid 1.2.1

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.
@@ -0,0 +1,47 @@
1
+ module Dynamoid
2
+ module Dirty
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Dirty
5
+
6
+ module ClassMethods
7
+ def from_database(*)
8
+ super.tap { |d| d.changed_attributes.clear }
9
+ end
10
+ end
11
+
12
+ def save(*)
13
+ clear_changes { super }
14
+ end
15
+
16
+ def update!(*)
17
+ ret = super
18
+ clear_changes #update! completely reloads all fields on the class, so any extant changes are wiped out
19
+ ret
20
+ end
21
+
22
+ def reload
23
+ super.tap { clear_changes }
24
+ end
25
+
26
+ def clear_changes
27
+ previous = changes
28
+ (block_given? ? yield : true).tap do |result|
29
+ unless result == false #failed validation; nil is OK.
30
+ @previously_changed = previous
31
+ changed_attributes.clear
32
+ end
33
+ end
34
+ end
35
+
36
+ def write_attribute(name, value)
37
+ attribute_will_change!(name) unless self.read_attribute(name) == value
38
+ super
39
+ end
40
+
41
+ protected
42
+
43
+ def attribute_method?(attr)
44
+ super || self.class.attributes.has_key?(attr.to_sym)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,201 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # This is the base module for all domain objects that need to be persisted to
5
+ # the database as documents.
6
+ module Document
7
+ extend ActiveSupport::Concern
8
+ include Dynamoid::Components
9
+
10
+ included do
11
+ class_attribute :options, :read_only_attributes, :base_class
12
+ self.options = {}
13
+ self.read_only_attributes = []
14
+ self.base_class = self
15
+
16
+ Dynamoid.included_models << self
17
+ end
18
+
19
+ module ClassMethods
20
+ # Set up table options, including naming it whatever you want, setting the id key, and manually overriding read and
21
+ # write capacity.
22
+ #
23
+ # @param [Hash] options options to pass for this table
24
+ # @option options [Symbol] :name the name for the table; this still gets namespaced
25
+ # @option options [Symbol] :id id column for the table
26
+ # @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
27
+ # @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
28
+ #
29
+ # @since 0.4.0
30
+ def table(options = {})
31
+ self.options = options
32
+ super if defined? super
33
+ end
34
+
35
+ def attr_readonly(*read_only_attributes)
36
+ self.read_only_attributes.concat read_only_attributes.map(&:to_s)
37
+ end
38
+
39
+ # Returns the read_capacity for this table.
40
+ #
41
+ # @since 0.4.0
42
+ def read_capacity
43
+ options[:read_capacity] || Dynamoid::Config.read_capacity
44
+ end
45
+
46
+ # Returns the write_capacity for this table.
47
+ #
48
+ # @since 0.4.0
49
+ def write_capacity
50
+ options[:write_capacity] || Dynamoid::Config.write_capacity
51
+ end
52
+
53
+ # Returns the id field for this class.
54
+ #
55
+ # @since 0.4.0
56
+ def hash_key
57
+ options[:key] || :id
58
+ end
59
+
60
+ # Returns the number of items for this class.
61
+ #
62
+ # @since 0.6.1
63
+ def count
64
+ Dynamoid.adapter.count(table_name)
65
+ end
66
+
67
+ # Initialize a new object and immediately save it to the database.
68
+ #
69
+ # @param [Hash] attrs Attributes with which to create the object.
70
+ #
71
+ # @return [Dynamoid::Document] the saved document
72
+ #
73
+ # @since 0.2.0
74
+ def create(attrs = {})
75
+ build(attrs).tap(&:save)
76
+ end
77
+
78
+ # Initialize a new object and immediately save it to the database. Raise an exception if persistence failed.
79
+ #
80
+ # @param [Hash] attrs Attributes with which to create the object.
81
+ #
82
+ # @return [Dynamoid::Document] the saved document
83
+ #
84
+ # @since 0.2.0
85
+ def create!(attrs = {})
86
+ build(attrs).tap(&:save!)
87
+ end
88
+
89
+ # Initialize a new object.
90
+ #
91
+ # @param [Hash] attrs Attributes with which to create the object.
92
+ #
93
+ # @return [Dynamoid::Document] the new document
94
+ #
95
+ # @since 0.2.0
96
+ def build(attrs = {})
97
+ attrs[:type] ? attrs[:type].constantize.new(attrs) : new(attrs)
98
+ end
99
+
100
+ # Does this object exist?
101
+ #
102
+ # @param [Mixed] id_or_conditions the id of the object or a hash with the options to filter from.
103
+ #
104
+ # @return [Boolean] true/false
105
+ #
106
+ # @since 0.2.0
107
+ def exists?(id_or_conditions = {})
108
+ case id_or_conditions
109
+ when Hash then ! where(id_or_conditions).all.empty?
110
+ else !! find(id_or_conditions)
111
+ end
112
+ end
113
+ end
114
+
115
+ # Initialize a new object.
116
+ #
117
+ # @param [Hash] attrs Attributes with which to create the object.
118
+ #
119
+ # @return [Dynamoid::Document] the new document
120
+ #
121
+ # @since 0.2.0
122
+ def initialize(attrs = {})
123
+ run_callbacks :initialize do
124
+ @new_record = true
125
+ @attributes ||= {}
126
+ @associations ||= {}
127
+
128
+ load(attrs)
129
+ end
130
+ end
131
+
132
+ def load(attrs)
133
+ self.class.undump(attrs).each do |key, value|
134
+ send("#{key}=", value) if self.respond_to?("#{key}=")
135
+ end
136
+ end
137
+
138
+ # An object is equal to another object if their ids are equal.
139
+ #
140
+ # @since 0.2.0
141
+ def ==(other)
142
+ if self.class.identity_map_on?
143
+ super
144
+ else
145
+ return false if other.nil?
146
+ other.is_a?(Dynamoid::Document) && self.hash_key == other.hash_key && self.range_value == other.range_value
147
+ end
148
+ end
149
+
150
+ def eql?(other)
151
+ self == other
152
+ end
153
+
154
+ def hash
155
+ hash_key.hash ^ range_value.hash
156
+ end
157
+
158
+ # Reload an object from the database -- if you suspect the object has changed in the datastore and you need those
159
+ # changes to be reflected immediately, you would call this method. This is a consistent read.
160
+ #
161
+ # @return [Dynamoid::Document] the document this method was called on
162
+ #
163
+ # @since 0.2.0
164
+ def reload
165
+ range_key_value = range_value ? dumped_range_value : nil
166
+ self.attributes = self.class.find(hash_key, :range_key => range_key_value, :consistent_read => true).attributes
167
+ @associations.values.each(&:reset)
168
+ self
169
+ end
170
+
171
+ # Return an object's hash key, regardless of what it might be called to the object.
172
+ #
173
+ # @since 0.4.0
174
+ def hash_key
175
+ self.send(self.class.hash_key)
176
+ end
177
+
178
+ # Assign an object's hash key, regardless of what it might be called to the object.
179
+ #
180
+ # @since 0.4.0
181
+ def hash_key=(value)
182
+ self.send("#{self.class.hash_key}=", value)
183
+ end
184
+
185
+ def range_value
186
+ if range_key = self.class.range_key
187
+ self.send(range_key)
188
+ end
189
+ end
190
+
191
+ def range_value=(value)
192
+ self.send("#{self.class.range_key}=", value)
193
+ end
194
+
195
+ private
196
+
197
+ def dumped_range_value
198
+ dump_field(range_value, self.class.attributes[self.class.range_key])
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # All the errors specific to Dynamoid. The goal is to mimic ActiveRecord.
5
+ module Errors
6
+
7
+ # Generic Dynamoid error
8
+ class Error < StandardError; end
9
+
10
+ class MissingRangeKey < Error; end
11
+
12
+ class MissingIndex < Error; end
13
+
14
+ # InvalidIndex is raised when an invalid index is specified, for example if
15
+ # specified key attribute(s) or projected attributes do not exist.
16
+ class InvalidIndex < Error
17
+ def initialize(item)
18
+ if (item.is_a? String)
19
+ super(item)
20
+ else
21
+ super("Validation failed: #{item.errors.full_messages.join(", ")}")
22
+ end
23
+ end
24
+ end
25
+
26
+ # This class is intended to be private to Dynamoid.
27
+ class ConditionalCheckFailedException < Error
28
+ attr_reader :inner_exception
29
+
30
+ def initialize(inner)
31
+ super
32
+ @inner_exception = inner
33
+ end
34
+ end
35
+
36
+ class RecordNotUnique < ConditionalCheckFailedException
37
+ attr_reader :original_exception
38
+
39
+ def initialize(original_exception, record)
40
+ super("Attempted to write record #{record} when its key already exists")
41
+ @original_exception = original_exception
42
+ end
43
+ end
44
+
45
+ class StaleObjectError < ConditionalCheckFailedException
46
+ attr_reader :record, :attempted_action
47
+
48
+ def initialize(record, attempted_action)
49
+ super("Attempted to #{attempted_action} a stale object #{record}")
50
+ @record = record
51
+ @attempted_action = attempted_action
52
+ end
53
+ end
54
+
55
+ class DocumentNotValid < Error
56
+ attr_reader :document
57
+
58
+ def initialize(document)
59
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
60
+ @document = document
61
+ end
62
+ end
63
+
64
+ class InvalidQuery < Error; end
65
+ end
66
+ end
@@ -0,0 +1,164 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+ # All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
4
+ # specified with field, then they will be ignored.
5
+ module Fields
6
+ extend ActiveSupport::Concern
7
+
8
+ PERMITTED_KEY_TYPES = [
9
+ :number,
10
+ :integer,
11
+ :string,
12
+ :datetime
13
+ ]
14
+
15
+ # Initialize the attributes we know the class has, in addition to our magic attributes: id, created_at, and updated_at.
16
+ included do
17
+ class_attribute :attributes
18
+ class_attribute :range_key
19
+
20
+ self.attributes = {}
21
+ field :created_at, :datetime
22
+ field :updated_at, :datetime
23
+
24
+ field :id #Default primary key
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ # Specify a field for a document.
30
+ #
31
+ # Its type determines how it is coerced when read in and out of the datastore.
32
+ # You can specify :integer, :number, :set, :array, :datetime, and :serialized,
33
+ # or specify a class that defines a serialization strategy.
34
+ #
35
+ # If you specify a class for field type, Dynamoid will serialize using
36
+ # `dynamoid_dump` or `dump` methods, and load using `dynamoid_load` or `load` methods.
37
+ #
38
+ # Default field type is :string.
39
+ #
40
+ # @param [Symbol] name the name of the field
41
+ # @param [Symbol] type the type of the field (refer to method description for details)
42
+ # @param [Hash] options any additional options for the field
43
+ #
44
+ # @since 0.2.0
45
+ def field(name, type = :string, options = {})
46
+ named = name.to_s
47
+ if type == :float
48
+ Dynamoid.logger.warn("Field type :float, which you declared for '#{name}', is deprecated in favor of :number.")
49
+ type = :number
50
+ end
51
+ self.attributes = attributes.merge(name => {:type => type}.merge(options))
52
+
53
+ define_method(named) { read_attribute(named) }
54
+ define_method("#{named}?") do
55
+ value = read_attribute(named)
56
+ case value
57
+ when true then true
58
+ when false, nil then false
59
+ else
60
+ !value.nil?
61
+ end
62
+ end
63
+ define_method("#{named}=") {|value| write_attribute(named, value) }
64
+ end
65
+
66
+ def range(name, type = :string)
67
+ field(name, type)
68
+ self.range_key = name
69
+ end
70
+
71
+ def table(options)
72
+ #a default 'id' column is created when Dynamoid::Document is included
73
+ unless(attributes.has_key? hash_key)
74
+ remove_field :id
75
+ field(hash_key)
76
+ end
77
+ end
78
+
79
+ def remove_field(field)
80
+ field = field.to_sym
81
+ attributes.delete(field) or raise "No such field"
82
+ remove_method field
83
+ remove_method :"#{field}="
84
+ remove_method :"#{field}?"
85
+ end
86
+ end
87
+
88
+ # You can access the attributes of an object directly on its attributes method, which is by default an empty hash.
89
+ attr_accessor :attributes
90
+ alias :raw_attributes :attributes
91
+
92
+ # Write an attribute on the object. Also marks the previous value as dirty.
93
+ #
94
+ # @param [Symbol] name the name of the field
95
+ # @param [Object] value the value to assign to that field
96
+ #
97
+ # @since 0.2.0
98
+ def write_attribute(name, value)
99
+ if (size = value.to_s.size) > MAX_ITEM_SIZE
100
+ Dynamoid.logger.warn "DynamoDB can't store items larger than #{MAX_ITEM_SIZE} and the #{name} field has a length of #{size}."
101
+ end
102
+
103
+ if association = @associations[name]
104
+ association.reset
105
+ end
106
+
107
+ attributes[name.to_sym] = value
108
+ end
109
+ alias :[]= :write_attribute
110
+
111
+ # Read an attribute from an object.
112
+ #
113
+ # @param [Symbol] name the name of the field
114
+ #
115
+ # @since 0.2.0
116
+ def read_attribute(name)
117
+ attributes[name.to_sym]
118
+ end
119
+ alias :[] :read_attribute
120
+
121
+ # Updates multiple attibutes at once, saving the object once the updates are complete.
122
+ #
123
+ # @param [Hash] attributes a hash of attributes to update
124
+ #
125
+ # @since 0.2.0
126
+ def update_attributes(attributes)
127
+ attributes.each {|attribute, value| self.write_attribute(attribute, value)} unless attributes.nil? || attributes.empty?
128
+ save
129
+ end
130
+
131
+ # Update a single attribute, saving the object afterwards.
132
+ #
133
+ # @param [Symbol] attribute the attribute to update
134
+ # @param [Object] value the value to assign it
135
+ #
136
+ # @since 0.2.0
137
+ def update_attribute(attribute, value)
138
+ write_attribute(attribute, value)
139
+ save
140
+ end
141
+
142
+ private
143
+
144
+ # Automatically called during the created callback to set the created_at time.
145
+ #
146
+ # @since 0.2.0
147
+ def set_created_at
148
+ self.created_at = DateTime.now if Dynamoid::Config.timestamps
149
+ end
150
+
151
+ # Automatically called during the save callback to set the updated_at time.
152
+ #
153
+ # @since 0.2.0
154
+ def set_updated_at
155
+ self.updated_at = DateTime.now if Dynamoid::Config.timestamps
156
+ end
157
+
158
+ def set_type
159
+ self.type ||= self.class.to_s if self.class.attributes[:type]
160
+ end
161
+
162
+ end
163
+
164
+ end