• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Dynamoid / dynamoid / 4031654843

pending completion
4031654843

push

github

GitHub
Merge pull request #630 from Dynamoid/add-ability-to-run-partiql-queries

742 of 830 branches covered (89.4%)

Branch coverage included in aggregate %.

43 of 43 new or added lines in 4 files covered. (100.0%)

2787 of 3090 relevant lines covered (90.19%)

750.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.45
/lib/dynamoid/document.rb
1
# frozen_string_literal: true
2

3
module Dynamoid
1✔
4
  # This is the base module for all domain objects that need to be persisted to
5
  # the database as documents.
6
  module Document
1✔
7
    extend ActiveSupport::Concern
1✔
8
    include Dynamoid::Components
1✔
9

10
    included do
1✔
11
      class_attribute :options, :read_only_attributes, :base_class, instance_accessor: false
878✔
12
      self.options = {}
878✔
13
      self.read_only_attributes = []
878✔
14
      self.base_class = self
878✔
15

16
      Dynamoid.included_models << self unless Dynamoid.included_models.include? self
878!
17
    end
18

19
    module ClassMethods
1✔
20
      def attr_readonly(*read_only_attributes)
1✔
21
        self.read_only_attributes.concat read_only_attributes.map(&:to_s)
×
22
      end
23

24
      # Returns the read capacity for this table.
25
      #
26
      # @return [Integer] read capacity units
27
      # @since 0.4.0
28
      def read_capacity
1✔
29
        options[:read_capacity] || Dynamoid::Config.read_capacity
2,721✔
30
      end
31

32
      # Returns the write_capacity for this table.
33
      #
34
      # @return [Integer] write capacity units
35
      # @since 0.4.0
36
      def write_capacity
1✔
37
        options[:write_capacity] || Dynamoid::Config.write_capacity
2,721✔
38
      end
39

40
      # Returns the billing (capacity) mode for this table.
41
      #
42
      # Could be either +provisioned+ or +on_demand+.
43
      #
44
      # @return [Symbol]
45
      def capacity_mode
1✔
46
        options[:capacity_mode] || Dynamoid::Config.capacity_mode
2,719✔
47
      end
48

49
      # Returns the field name used to support STI for this table.
50
      #
51
      # Default field name is +type+ but it can be overrided in the +table+
52
      # method call.
53
      #
54
      #   User.inheritance_field # => :type
55
      def inheritance_field
1✔
56
        options[:inheritance_field] || :type
7,086✔
57
      end
58

59
      # Returns the hash key field name for this class.
60
      #
61
      # By default +id+ field is used. But it can be overriden in the +table+
62
      # method call.
63
      #
64
      #   User.hash_key # => :id
65
      #
66
      # @return [Symbol] a hash key name
67
      # @since 0.4.0
68
      def hash_key
1✔
69
        options[:key] || :id
58,446✔
70
      end
71

72
      # Return the count of items for this class.
73
      #
74
      # It returns approximate value based on DynamoDB statistic. DynamoDB
75
      # updates it periodically so the value can be no accurate.
76
      #
77
      # It's a reletively cheap operation and doesn't read all the items in a
78
      # table. It makes just one HTTP request to DynamoDB.
79
      #
80
      # @return [Integer] items count in a table
81
      # @since 0.6.1
82
      def count
1✔
83
        Dynamoid.adapter.count(table_name)
33✔
84
      end
85

86
      # Initialize a new object.
87
      #
88
      #   User.build(name: 'A')
89
      #
90
      # Initialize an object and pass it into a block to set other attributes.
91
      #
92
      #   User.build(name: 'A') do |u|
93
      #     u.age = 21
94
      #   end
95
      #
96
      # The only difference between +build+ and +new+ methods is that +build+
97
      # supports STI (Single table inheritance) and looks at the inheritance
98
      # field. So it can build a model of actual class. For instance:
99
      #
100
      #   class Employee
101
      #     include Dynamoid::Document
102
      #
103
      #     field :type
104
      #     field :name
105
      #   end
106
      #
107
      #   class Manager < Employee
108
      #   end
109
      #
110
      #   Employee.build(name: 'Alice', type: 'Manager') # => #<Manager:0x00007f945756e3f0 ...>
111
      #
112
      # @param attrs [Hash] Attributes with which to create the document
113
      # @param block [Proc] Block to process a document after initialization
114
      # @return [Dynamoid::Document] the new document
115
      # @since 0.2.0
116
      def build(attrs = {}, &block)
1✔
117
        choose_right_class(attrs).new(attrs, &block)
2,033✔
118
      end
119

120
      # Does this model exist in a table?
121
      #
122
      #   User.exists?('713') # => true
123
      #
124
      # If a range key is declared it should be specified in the following way:
125
      #
126
      #   User.exists?([['713', 'range-key-value']]) # => true
127
      #
128
      # It's possible to check existence of several models at once:
129
      #
130
      #   User.exists?(['713', '714', '715'])
131
      #
132
      # Or in case when a range key is declared:
133
      #
134
      #   User.exists?(
135
      #     [
136
      #       ['713', 'range-key-value-1'],
137
      #       ['714', 'range-key-value-2'],
138
      #       ['715', 'range-key-value-3']
139
      #     ]
140
      #   )
141
      #
142
      # It's also possible to specify models not with primary key but with
143
      # conditions on the attributes (in the +where+ method style):
144
      #
145
      #   User.exists?(age: 20, 'created_at.gt': Time.now - 1.day)
146
      #
147
      # @param id_or_conditions [String|Array[String]|Array[Array]|Hash] the primary id of the model, a list of primary ids or a hash with the options to filter from.
148
      # @return [true|false]
149
      # @since 0.2.0
150
      def exists?(id_or_conditions = {})
1✔
151
        case id_or_conditions
11✔
152
        when Hash then where(id_or_conditions).count >= 1
4✔
153
        else
154
          begin
7✔
155
            find(id_or_conditions)
7✔
156
            true
4✔
157
          rescue Dynamoid::Errors::RecordNotFound
3✔
158
            false
3✔
159
          end
160
        end
161
      end
162

163
      # @private
164
      def deep_subclasses
1✔
165
        subclasses + subclasses.map(&:deep_subclasses).flatten
32✔
166
      end
167

168
      # @private
169
      def choose_right_class(attrs)
1✔
170
        attrs[inheritance_field] ? attrs[inheritance_field].constantize : self
3,180✔
171
      end
172
    end
173

174
    # Initialize a new object.
175
    #
176
    #   User.new(name: 'A')
177
    #
178
    # Initialize an object and pass it into a block to set other attributes.
179
    #
180
    #   User.new(name: 'A') do |u|
181
    #     u.age = 21
182
    #   end
183
    #
184
    # @param attrs [Hash] Attributes with which to create the document
185
    # @param block [Proc] Block to process a document after initialization
186
    # @return [Dynamoid::Document] the new document
187
    #
188
    # @since 0.2.0
189
    def initialize(attrs = {}, &block)
1✔
190
      run_callbacks :initialize do
3,480✔
191
        @new_record = true
3,480✔
192
        @attributes ||= {}
3,480✔
193
        @associations ||= {}
3,480✔
194
        @attributes_before_type_cast ||= {}
3,480✔
195

196
        attrs_with_defaults = self.class.attributes.each_with_object({}) do |(attribute, options), res|
3,480✔
197
          if attrs.key?(attribute)
25,826✔
198
            res[attribute] = attrs[attribute]
9,089✔
199
          elsif options.key?(:default)
16,737✔
200
            res[attribute] = evaluate_default_value(options[:default])
46✔
201
          end
202
        end
203

204
        attrs_virtual = attrs.slice(*(attrs.keys - self.class.attributes.keys))
3,480✔
205

206
        load(attrs_with_defaults.merge(attrs_virtual))
3,480✔
207

208
        if block
3,479✔
209
          yield(self)
7✔
210
        end
211
      end
212
    end
213

214
    # Check equality of two models.
215
    #
216
    # A model is equal to another model only if their primary keys (hash key
217
    # and optionally range key) are equal.
218
    #
219
    # @return [true|false]
220
    # @since 0.2.0
221
    def ==(other)
1✔
222
      if self.class.identity_map_on?
20,674!
223
        super
×
224
      else
20,674✔
225
        return false if other.nil?
20,674✔
226

227
        other.is_a?(Dynamoid::Document) && hash_key == other.hash_key && range_value == other.range_value
20,655✔
228
      end
229
    end
230

231
    # Check equality of two models.
232
    #
233
    # Works exactly like +==+ does.
234
    #
235
    # @return [true|false]
236
    def eql?(other)
1✔
237
      self == other
20✔
238
    end
239

240
    # Generate an Integer hash value for this model.
241
    #
242
    # Hash value is based on primary key. So models can be used safely as a
243
    # +Hash+ keys.
244
    #
245
    # @return [Integer]
246
    def hash
1✔
247
      hash_key.hash ^ range_value.hash
120✔
248
    end
249

250
    # Return a model's hash key value.
251
    #
252
    # @since 0.4.0
253
    def hash_key
1✔
254
      self[self.class.hash_key.to_sym]
44,497✔
255
    end
256

257
    # Assign a model's hash key value, regardless of what it might be called to
258
    # the object.
259
    #
260
    # @since 0.4.0
261
    def hash_key=(value)
1✔
262
      self[self.class.hash_key.to_sym] = value
1,290✔
263
    end
264

265
    # Return a model's range key value.
266
    #
267
    # Returns +nil+ if a range key isn't declared for a model.
268
    def range_value
1✔
269
      if self.class.range_key
1,067✔
270
        self[self.class.range_key.to_sym]
292✔
271
      end
272
    end
273

274
    # Assign a model's range key value.
275
    def range_value=(value)
1✔
276
      if self.class.range_key
2!
277
        self[self.class.range_key.to_sym] = value
2✔
278
      end
279
    end
280

281
    private
1✔
282

283
    def dumped_range_value
1✔
284
      Dumping.dump_field(range_value, self.class.attributes[self.class.range_key])
×
285
    end
286

287
    # Evaluates the default value given, this is used by undump
288
    # when determining the value of the default given for a field options.
289
    #
290
    # @param val [Object] the attribute's default value
291
    def evaluate_default_value(val)
1✔
292
      if val.respond_to?(:call)
46✔
293
        val.call
9✔
294
      elsif val.duplicable?
37✔
295
        val.dup
37✔
296
      else
×
297
        val
×
298
      end
299
    end
300
  end
301
end
302

303
ActiveSupport.run_load_hooks(:dynamoid, Dynamoid::Document)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc