synamoid 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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