cequel 1.0.0.rc1 → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/cequel.rb +18 -0
  3. data/lib/cequel/errors.rb +8 -4
  4. data/lib/cequel/metal.rb +14 -0
  5. data/lib/cequel/metal/batch.rb +21 -11
  6. data/lib/cequel/metal/batch_manager.rb +74 -0
  7. data/lib/cequel/metal/cql_row_specification.rb +19 -6
  8. data/lib/cequel/metal/data_set.rb +400 -163
  9. data/lib/cequel/metal/deleter.rb +45 -11
  10. data/lib/cequel/metal/incrementer.rb +23 -10
  11. data/lib/cequel/metal/inserter.rb +19 -6
  12. data/lib/cequel/metal/keyspace.rb +82 -159
  13. data/lib/cequel/metal/logger.rb +71 -0
  14. data/lib/cequel/metal/logging.rb +47 -0
  15. data/lib/cequel/metal/new_relic_instrumentation.rb +26 -0
  16. data/lib/cequel/metal/row.rb +36 -10
  17. data/lib/cequel/metal/row_specification.rb +21 -8
  18. data/lib/cequel/metal/statement.rb +30 -6
  19. data/lib/cequel/metal/updater.rb +89 -12
  20. data/lib/cequel/metal/writer.rb +23 -14
  21. data/lib/cequel/record.rb +52 -6
  22. data/lib/cequel/record/association_collection.rb +13 -6
  23. data/lib/cequel/record/associations.rb +146 -54
  24. data/lib/cequel/record/belongs_to_association.rb +34 -7
  25. data/lib/cequel/record/bound.rb +69 -12
  26. data/lib/cequel/record/bulk_writes.rb +29 -1
  27. data/lib/cequel/record/callbacks.rb +22 -6
  28. data/lib/cequel/record/collection.rb +273 -36
  29. data/lib/cequel/record/configuration_generator.rb +5 -0
  30. data/lib/cequel/record/data_set_builder.rb +86 -0
  31. data/lib/cequel/record/dirty.rb +11 -8
  32. data/lib/cequel/record/errors.rb +38 -4
  33. data/lib/cequel/record/has_many_association.rb +42 -9
  34. data/lib/cequel/record/lazy_record_collection.rb +39 -10
  35. data/lib/cequel/record/mass_assignment.rb +14 -6
  36. data/lib/cequel/record/persistence.rb +157 -20
  37. data/lib/cequel/record/properties.rb +147 -24
  38. data/lib/cequel/record/railtie.rb +15 -2
  39. data/lib/cequel/record/record_set.rb +504 -75
  40. data/lib/cequel/record/schema.rb +77 -13
  41. data/lib/cequel/record/scoped.rb +16 -11
  42. data/lib/cequel/record/secondary_indexes.rb +42 -6
  43. data/lib/cequel/record/tasks.rb +2 -1
  44. data/lib/cequel/record/validations.rb +51 -11
  45. data/lib/cequel/schema.rb +9 -0
  46. data/lib/cequel/schema/column.rb +172 -33
  47. data/lib/cequel/schema/create_table_dsl.rb +62 -31
  48. data/lib/cequel/schema/keyspace.rb +106 -7
  49. data/lib/cequel/schema/migration_validator.rb +128 -0
  50. data/lib/cequel/schema/table.rb +183 -20
  51. data/lib/cequel/schema/table_property.rb +92 -34
  52. data/lib/cequel/schema/table_reader.rb +45 -15
  53. data/lib/cequel/schema/table_synchronizer.rb +101 -43
  54. data/lib/cequel/schema/table_updater.rb +114 -19
  55. data/lib/cequel/schema/table_writer.rb +31 -13
  56. data/lib/cequel/schema/update_table_dsl.rb +71 -40
  57. data/lib/cequel/type.rb +214 -53
  58. data/lib/cequel/util.rb +6 -9
  59. data/lib/cequel/version.rb +2 -1
  60. data/spec/examples/record/associations_spec.rb +12 -12
  61. data/spec/examples/record/persistence_spec.rb +5 -5
  62. data/spec/examples/record/record_set_spec.rb +62 -50
  63. data/spec/examples/schema/table_synchronizer_spec.rb +37 -11
  64. data/spec/examples/schema/table_updater_spec.rb +3 -3
  65. data/spec/examples/spec_helper.rb +2 -11
  66. data/spec/examples/type_spec.rb +3 -3
  67. metadata +23 -4
  68. data/lib/cequel/new_relic_instrumentation.rb +0 -22
@@ -1,5 +1,10 @@
1
1
  module Cequel
2
2
  module Record
3
+ #
4
+ # Rails generator for a default configuration file
5
+ #
6
+ # @since 1.0.0
7
+ #
3
8
  class ConfigurationGenerator < Rails::Generators::Base
4
9
  namespace 'cequel:configuration'
5
10
  source_root File.expand_path('../../../../templates/', __FILE__)
@@ -0,0 +1,86 @@
1
+ module Cequel
2
+ module Record
3
+ #
4
+ # This is a utility class to construct a {Metal::DataSet} for a given
5
+ # {RecordSet}.
6
+ #
7
+ # @api private
8
+ #
9
+ class DataSetBuilder
10
+ extend Forwardable
11
+
12
+ #
13
+ # Build a data set for the given record set
14
+ #
15
+ # @param (see #initialize)
16
+ # @return [Metal::DataSet] a DataSet exposing the rows for the record set
17
+ #
18
+ def self.build_for(record_set)
19
+ new(record_set).build
20
+ end
21
+
22
+ #
23
+ # @param record_set [RecordSet] record set for which to construct data
24
+ # set
25
+ #
26
+ def initialize(record_set)
27
+ @record_set = record_set
28
+ @data_set = record_set.connection[record_set.target_class.table_name]
29
+ end
30
+ private_class_method :new
31
+
32
+ def build
33
+ add_limit
34
+ add_select_columns
35
+ add_where_statement
36
+ add_bounds
37
+ add_order
38
+ data_set
39
+ end
40
+
41
+ protected
42
+
43
+ attr_accessor :data_set
44
+ attr_reader :record_set
45
+ def_delegators :record_set, :row_limit, :select_columns,
46
+ :scoped_key_names, :scoped_key_values,
47
+ :scoped_indexed_column, :lower_bound,
48
+ :upper_bound, :reversed?, :order_by_column
49
+
50
+ private
51
+
52
+ def add_limit
53
+ self.data_set = data_set.limit(row_limit) if row_limit
54
+ end
55
+
56
+ def add_select_columns
57
+ self.data_set = data_set.select(*select_columns) if select_columns
58
+ end
59
+
60
+ def add_where_statement
61
+ if scoped_key_values
62
+ key_conditions = Hash[scoped_key_names.zip(scoped_key_values)]
63
+ self.data_set = data_set.where(key_conditions)
64
+ end
65
+ if scoped_indexed_column
66
+ self.data_set = data_set.where(scoped_indexed_column)
67
+ end
68
+ end
69
+
70
+ def add_bounds
71
+ if lower_bound
72
+ self.data_set =
73
+ data_set.where(*lower_bound.to_cql_with_bind_variables)
74
+ end
75
+ if upper_bound
76
+ self.data_set =
77
+ data_set.where(*upper_bound.to_cql_with_bind_variables)
78
+ end
79
+ end
80
+
81
+ def add_order
82
+ self.data_set = data_set.order(order_by_column => :desc) if reversed?
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,15 +1,21 @@
1
1
  module Cequel
2
-
3
2
  module Record
4
-
3
+ #
4
+ # Cequel provides support for dirty attribute tracking via ActiveModel.
5
+ # Modifications to collection columns are registered by this mechanism.
6
+ #
7
+ # @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html Rails
8
+ # documentation for ActiveModel::Dirty
9
+ #
10
+ # @since 0.1.0
11
+ #
5
12
  module Dirty
6
-
7
13
  extend ActiveSupport::Concern
8
14
 
9
15
  included { include ActiveModel::Dirty }
10
16
 
17
+ # @private
11
18
  module ClassMethods
12
-
13
19
  def key(name, *)
14
20
  define_attribute_method(name)
15
21
  super
@@ -34,9 +40,9 @@ module Cequel
34
40
  define_attribute_method(name)
35
41
  super
36
42
  end
37
-
38
43
  end
39
44
 
45
+ # @private
40
46
  def save(options = {})
41
47
  super.tap do |success|
42
48
  if success
@@ -54,9 +60,6 @@ module Cequel
54
60
  end
55
61
  super
56
62
  end
57
-
58
63
  end
59
-
60
64
  end
61
-
62
65
  end
@@ -1,14 +1,48 @@
1
1
  module Cequel
2
-
3
2
  module Record
4
-
3
+ #
4
+ # Raised when attempting to access an attribute of a record when that
5
+ # attribute hasn't been loaded
6
+ #
7
+ # @since 1.0.0
8
+ #
5
9
  MissingAttributeError = Class.new(ArgumentError)
10
+ #
11
+ # Raised when attempting to read or write an attribute that isn't defined
12
+ # on the record
13
+ #
14
+ # @since 1.0.0
15
+ #
6
16
  UnknownAttributeError = Class.new(ArgumentError)
17
+ #
18
+ # Raised when attempting to load a record by key when that record does not
19
+ # exist
20
+ #
7
21
  RecordNotFound = Class.new(StandardError)
22
+ #
23
+ # Raised when attempting to configure a record in a way that is not
24
+ # possible
25
+ #
26
+ # @since 1.0.0
27
+ #
8
28
  InvalidRecordConfiguration = Class.new(StandardError)
29
+ #
30
+ # Raised when attempting to save a record that is invalid
31
+ #
9
32
  RecordInvalid = Class.new(StandardError)
33
+ #
34
+ # Raised when attempting to construct a {RecordSet} that cannot construct
35
+ # a valid CQL query
36
+ #
37
+ # @since 1.0.0
38
+ #
10
39
  IllegalQuery = Class.new(StandardError)
11
-
40
+ #
41
+ # Raised when attempting to persist a Cequel::Record without defining all
42
+ # primary key columns
43
+ #
44
+ # @since 1.0.0
45
+ #
46
+ MissingKeyError = Class.new(StandardError)
12
47
  end
13
-
14
48
  end
@@ -1,26 +1,59 @@
1
1
  module Cequel
2
-
3
2
  module Record
4
-
3
+ #
4
+ # Represents a child association declared by
5
+ # {Associations::ClassMethods#has_many has_many}.
6
+ #
7
+ # @see Associations::ClassMethods#child_associations
8
+ # @since 1.0.0
9
+ #
5
10
  class HasManyAssociation
6
-
7
- attr_reader :owner_class, :name, :association_class_name
8
-
11
+ # @return [Class] Record class that declares this association
12
+ attr_reader :owner_class
13
+ # @return [Symbol] name of this association
14
+ attr_reader :name
15
+ # @return [Symbol] name of the child class that this association contains
16
+ attr_reader :association_class_name
17
+ # @return [Boolean] behavior for propagating destruction from parent to
18
+ # children
19
+ attr_reader :dependent
20
+
21
+ #
22
+ # @param owner_class [Class] Record class that declares this association
23
+ # @param name [Symbol] name of the association
24
+ # @param options [Options] options for the association
25
+ # @option options [Symbol] :class_name name of the child class
26
+ # @option options [Boolean] :dependent propagation behavior for destroy
27
+ #
28
+ # @api private
29
+ #
9
30
  def initialize(owner_class, name, options = {})
31
+ options.assert_valid_keys(:class_name, :dependent)
32
+
10
33
  @owner_class, @name = owner_class, name
11
- @association_class_name = options.fetch(:class_name, name.to_s.classify)
34
+ @association_class_name =
35
+ options.fetch(:class_name, name.to_s.classify)
36
+ case options[:dependent]
37
+ when :destroy, :delete, nil
38
+ @dependent = options[:dependent]
39
+ else
40
+ fail ArgumentError,
41
+ "Invalid :dependent option #{options[:dependent].inspect}. " \
42
+ "Valid values are :destroy, :delete"
43
+ end
12
44
  end
13
45
 
46
+ #
47
+ # @return [Class] class of child association
48
+ #
14
49
  def association_class
15
50
  @association_class ||= association_class_name.constantize
16
51
  end
17
52
 
53
+ # @private
18
54
  def instance_variable_name
19
55
  @instance_variable_name ||= :"@#{name}"
20
56
  end
21
-
22
57
  end
23
-
24
58
  end
25
-
26
59
  end
@@ -1,19 +1,36 @@
1
1
  module Cequel
2
-
3
2
  module Record
4
-
3
+ #
4
+ # Encapsulates a collection of unloaded {Record} instances. In the case
5
+ # where a record set is scoped to fully specify the keys of multiple
6
+ # records, those records will be returned unloaded in a
7
+ # LazyRecordCollection. When an attribute is read from any of the records
8
+ # in a LazyRecordCollection, it will eagerly load all of the records' rows
9
+ # from the database.
10
+ #
11
+ # @since 1.0.0
12
+ #
5
13
  class LazyRecordCollection < DelegateClass(Array)
6
-
7
14
  extend Forwardable
8
15
  include BulkWrites
9
-
16
+ #
17
+ # @!method table
18
+ # (see RecordSet#table)
19
+ # @!method connection
20
+ # (see RecordSet#connection)
10
21
  def_delegators :record_set, :table, :connection
11
22
 
23
+ #
24
+ # @param record_set [RecordSet] record set representing the records in
25
+ # this collection
26
+ # @api private
27
+ #
12
28
  def initialize(record_set)
13
- raise ArgumentError if record_set.nil?
29
+ fail ArgumentError if record_set.nil?
30
+ @record_set = record_set
14
31
 
15
32
  exploded_key_attributes = [{}].tap do |all_key_attributes|
16
- record_set.key_columns.zip(record_set.scoped_key_attributes.values) do |column, values|
33
+ key_columns.zip(scoped_key_values) do |column, values|
17
34
  all_key_attributes.replace(Array(values).flat_map do |value|
18
35
  all_key_attributes.map do |key_attributes|
19
36
  key_attributes.merge(column.name => value)
@@ -27,9 +44,12 @@ module Cequel
27
44
  end
28
45
 
29
46
  super(unloaded_records)
30
- @record_set = record_set
31
47
  end
32
48
 
49
+ #
50
+ # Hydrate all the records in this collection from a database query
51
+ #
52
+ # @return [LazyRecordCollection] self
33
53
  def load!
34
54
  records_by_identity = index_by { |record| record.key_values }
35
55
 
@@ -37,17 +57,26 @@ module Cequel
37
57
  identity = row.values_at(*record_set.key_column_names)
38
58
  records_by_identity[identity].hydrate(row)
39
59
  end
60
+
61
+ loaded_count = count { |record| record.loaded? }
62
+ if loaded_count < count
63
+ fail Cequel::Record::RecordNotFound,
64
+ "Expected #{count} results; got #{loaded_count}"
65
+ end
66
+
67
+ self
40
68
  end
41
69
 
42
70
  private
71
+
43
72
  attr_reader :record_set
44
73
 
74
+ def_delegators :record_set, :key_columns, :scoped_key_values
75
+ private :key_columns, :scoped_key_values
76
+
45
77
  def key_attributes_for_each_row
46
78
  map { |record| record.key_attributes }
47
79
  end
48
-
49
80
  end
50
-
51
81
  end
52
-
53
82
  end
@@ -5,11 +5,21 @@ rescue LoadError
5
5
  end
6
6
 
7
7
  module Cequel
8
-
9
8
  module Record
10
-
9
+ #
10
+ # Cequel supports mass-assignment protection in both the Rails 3 and Rails
11
+ # 4 paradigms. Rails 3 applications may define `attr_protected` and
12
+ # `attr_accessible` attributes in {Record} classes. In Rails 4, Cequel will
13
+ # respect strong parameters.
14
+ #
15
+ # @see https://github.com/rails/strong_parameters Rails 4 Strong Parameters
16
+ # @see
17
+ # http://api.rubyonrails.org/v3.2.15/classes/ActiveModel/MassAssignmentSecurity.html
18
+ # Rails 3 mass-assignment security
19
+ #
20
+ # @since 1.0.0
21
+ #
11
22
  module MassAssignment
12
-
13
23
  extend ActiveSupport::Concern
14
24
 
15
25
  included do
@@ -20,12 +30,10 @@ module Cequel
20
30
  end
21
31
  end
22
32
 
33
+ # @private
23
34
  def attributes=(attributes)
24
35
  super(sanitize_for_mass_assignment(attributes))
25
36
  end
26
-
27
37
  end
28
-
29
38
  end
30
-
31
39
  end
@@ -1,68 +1,173 @@
1
1
  module Cequel
2
-
3
2
  module Record
4
-
3
+ #
4
+ # This module provides functionality for loading and saving records to the
5
+ # Cassandra database.
6
+ #
7
+ # @see ClassMethods
8
+ #
9
+ # @since 0.1.0
10
+ #
5
11
  module Persistence
6
-
7
12
  extend ActiveSupport::Concern
8
13
  extend Forwardable
9
14
 
15
+ #
16
+ # Class-level functionality for loading and saving records
17
+ #
10
18
  module ClassMethods
11
-
12
19
  extend Forwardable
13
- def_delegator 'Cequel::Record', :connection
14
20
 
21
+ #
22
+ # Initialize a new record instance, assign attributes, and immediately
23
+ # save it.
24
+ #
25
+ # @param attributes [Hash] attributes to assign to the new record
26
+ # @yieldparam record [Record] record to make modifications before
27
+ # saving
28
+ # @return [Record] self
29
+ #
30
+ # @example Create a new record with attribute assignment
31
+ # Post.create(
32
+ # blog_subdomain: 'cassandra',
33
+ # permalink: 'cequel',
34
+ # title: 'Cequel: The Next Generation'
35
+ # )
36
+ #
37
+ # @example Create a new record with a block
38
+ # Post.create do |post|
39
+ # post.blog = blog
40
+ # post.permalink = 'cequel'
41
+ # post.title = 'Cequel: The Next Generation'
42
+ # end
43
+ #
15
44
  def create(attributes = {}, &block)
16
45
  new(attributes, &block).tap { |record| record.save }
17
46
  end
18
47
 
48
+ # @private
19
49
  def table
20
50
  connection[table_name]
21
51
  end
22
52
 
53
+ # @private
23
54
  def hydrate(row)
24
55
  new_empty(row).__send__(:hydrated!)
25
56
  end
26
57
 
58
+ # @private
59
+ def_delegator 'Cequel::Record', :connection
27
60
  end
28
61
 
29
- def_delegators 'self.class', :connection, :table
30
-
62
+ #
63
+ # @return [Hash] the attributes of this record that make up the primary
64
+ # key
65
+ #
66
+ # @example
67
+ # post = Post.new
68
+ # post.blog_subdomain = 'cassandra'
69
+ # post.permalink = 'cequel'
70
+ # post.title = 'Cequel: The Next Generation'
71
+ # post.key_attributes
72
+ # #=> {:blog_subdomain=>'cassandra', :permalink=>'cequel'}
73
+ #
74
+ # @since 1.0.0
75
+ #
31
76
  def key_attributes
32
77
  @attributes.slice(*self.class.key_column_names)
33
78
  end
34
79
 
80
+ #
81
+ # @return [Array] the values of the primary key columns for this record
82
+ #
83
+ # @see #key_attributes
84
+ # @since 1.0.0
85
+ #
35
86
  def key_values
36
87
  key_attributes.values
37
88
  end
38
89
 
90
+ #
91
+ # Check if an unloaded record exists in the database
92
+ #
93
+ # @return `true` if the record has a corresponding row in the
94
+ # database
95
+ #
96
+ # @since 1.0.0
97
+ #
39
98
  def exists?
40
99
  load!
41
100
  true
42
101
  rescue RecordNotFound
43
102
  false
44
103
  end
45
- alias :exist? :exists?
46
-
104
+ alias_method :exist?, :exists?
105
+
106
+ #
107
+ # Load an unloaded record's row from the database and hydrate the
108
+ # record's attributes
109
+ #
110
+ # @return [Record] self
111
+ #
112
+ # @since 1.0.0
113
+ #
47
114
  def load
48
115
  assert_keys_present!
49
116
  record_collection.load! unless loaded?
50
117
  self
51
118
  end
52
119
 
120
+ #
121
+ # Attempt to load an unloaded record and raise an error if the record
122
+ # does not correspond to a row in the database
123
+ #
124
+ # @return [Record] self
125
+ # @raise [RecordNotFound] if row does not exist in the database
126
+ #
127
+ # @see #load
128
+ # @since 1.0.0
129
+ #
53
130
  def load!
54
131
  load.tap do
55
132
  if transient?
56
- raise RecordNotFound,
57
- "Couldn't find #{self.class.name} with #{key_attributes.inspect}"
133
+ fail RecordNotFound,
134
+ "Couldn't find #{self.class.name} with " \
135
+ "#{key_attributes.inspect}"
58
136
  end
59
137
  end
60
138
  end
61
139
 
140
+ #
141
+ # @overload loaded?
142
+ # @return [Boolean] true if this record's attributes have been loaded
143
+ # from the database
144
+ #
145
+ # @overload loaded?(column)
146
+ # @param [Symbol] column name of column to check if loaded
147
+ # @return [Boolean] true if the named column is loaded in memory
148
+ #
149
+ # @return [Boolean]
150
+ #
151
+ # @since 1.0.0
152
+ #
62
153
  def loaded?(column = nil)
63
154
  !!@loaded && (column.nil? || @attributes.key?(column.to_sym))
64
155
  end
65
156
 
157
+ #
158
+ # Persist the record to the database. If this is a new record, it will
159
+ # be saved using an INSERT statement. If it is an existing record, it
160
+ # will be persisted using a series of `UPDATE` and `DELETE` statements
161
+ # which will persist all changes to the database, including atomic
162
+ # collection modifications.
163
+ #
164
+ # @param options [Options] options for save
165
+ # @option options [Boolean] :validate (true) whether to run validations
166
+ # before saving
167
+ # @return [Boolean] true if record saved successfully, false if invalid
168
+ #
169
+ # @see Validations#save!
170
+ #
66
171
  def save(options = {})
67
172
  options.assert_valid_keys
68
173
  if new_record? then create
@@ -72,11 +177,26 @@ module Cequel
72
177
  true
73
178
  end
74
179
 
180
+ #
181
+ # Set attributes and save the record
182
+ #
183
+ # @param attributes [Hash] hash of attributes to update
184
+ # @return [Boolean] true if saved successfully
185
+ #
186
+ # @see #save
187
+ # @see Properties#attributes=
188
+ # @see Validations#update_attributes!
189
+ #
75
190
  def update_attributes(attributes)
76
191
  self.attributes = attributes
77
192
  save
78
193
  end
79
194
 
195
+ #
196
+ # Remove this record from the database
197
+ #
198
+ # @return [Record] self
199
+ #
80
200
  def destroy
81
201
  assert_keys_present!
82
202
  metal_scope.delete
@@ -84,18 +204,34 @@ module Cequel
84
204
  self
85
205
  end
86
206
 
207
+ #
208
+ # @return true if this is a new, unsaved record
209
+ #
210
+ # @since 1.0.0
211
+ #
87
212
  def new_record?
88
213
  !!@new_record
89
214
  end
90
215
 
216
+ #
217
+ # @return true if this record is persisted in the database
218
+ #
219
+ # @see #transient?
220
+ #
91
221
  def persisted?
92
222
  !!@persisted
93
223
  end
94
224
 
225
+ #
226
+ # @return true if this record is not persisted in the database
227
+ #
228
+ # @see persisted?
229
+ #
95
230
  def transient?
96
231
  !persisted?
97
232
  end
98
233
 
234
+ # @private
99
235
  def hydrate(row)
100
236
  @attributes = row
101
237
  hydrated!
@@ -140,6 +276,9 @@ module Cequel
140
276
 
141
277
  private
142
278
 
279
+ def_delegators 'self.class', :connection, :table
280
+ private :connection, :table
281
+
143
282
  def read_attribute(attribute)
144
283
  super
145
284
  rescue MissingAttributeError
@@ -149,13 +288,14 @@ module Cequel
149
288
 
150
289
  def write_attribute(name, value)
151
290
  column = self.class.reflect_on_column(name)
152
- raise UnknownAttributeError, "unknown attribute: #{name}" unless column
291
+ fail UnknownAttributeError, "unknown attribute: #{name}" unless column
153
292
  value = column.cast(value) unless value.nil?
154
293
 
155
294
  super.tap do
156
295
  unless new_record?
157
296
  if key_attributes.keys.include?(name)
158
- raise ArgumentError, "Can't update key #{name} on persisted record"
297
+ fail ArgumentError,
298
+ "Can't update key #{name} on persisted record"
159
299
  end
160
300
 
161
301
  if value.nil?
@@ -169,8 +309,8 @@ module Cequel
169
309
 
170
310
  def record_collection
171
311
  @record_collection ||=
172
- LazyRecordCollection.new(self.class.at(*key_values)).
173
- tap { |set| set.__setobj__([self]) }
312
+ LazyRecordCollection.new(self.class.at(*key_values))
313
+ .tap { |set| set.__setobj__([self]) }
174
314
  end
175
315
 
176
316
  def hydrated!
@@ -206,13 +346,10 @@ module Cequel
206
346
  def assert_keys_present!
207
347
  missing_keys = key_attributes.select { |k, v| v.nil? }
208
348
  if missing_keys.any?
209
- raise MissingKeyError,
210
- "Missing required key values: #{missing_keys.keys.join(', ')}"
349
+ fail MissingKeyError,
350
+ "Missing required key values: #{missing_keys.keys.join(', ')}"
211
351
  end
212
352
  end
213
-
214
353
  end
215
-
216
354
  end
217
-
218
355
  end