cequel 1.0.0.rc1 → 1.0.0.rc2
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.
- checksums.yaml +4 -4
- data/lib/cequel.rb +18 -0
- data/lib/cequel/errors.rb +8 -4
- data/lib/cequel/metal.rb +14 -0
- data/lib/cequel/metal/batch.rb +21 -11
- data/lib/cequel/metal/batch_manager.rb +74 -0
- data/lib/cequel/metal/cql_row_specification.rb +19 -6
- data/lib/cequel/metal/data_set.rb +400 -163
- data/lib/cequel/metal/deleter.rb +45 -11
- data/lib/cequel/metal/incrementer.rb +23 -10
- data/lib/cequel/metal/inserter.rb +19 -6
- data/lib/cequel/metal/keyspace.rb +82 -159
- data/lib/cequel/metal/logger.rb +71 -0
- data/lib/cequel/metal/logging.rb +47 -0
- data/lib/cequel/metal/new_relic_instrumentation.rb +26 -0
- data/lib/cequel/metal/row.rb +36 -10
- data/lib/cequel/metal/row_specification.rb +21 -8
- data/lib/cequel/metal/statement.rb +30 -6
- data/lib/cequel/metal/updater.rb +89 -12
- data/lib/cequel/metal/writer.rb +23 -14
- data/lib/cequel/record.rb +52 -6
- data/lib/cequel/record/association_collection.rb +13 -6
- data/lib/cequel/record/associations.rb +146 -54
- data/lib/cequel/record/belongs_to_association.rb +34 -7
- data/lib/cequel/record/bound.rb +69 -12
- data/lib/cequel/record/bulk_writes.rb +29 -1
- data/lib/cequel/record/callbacks.rb +22 -6
- data/lib/cequel/record/collection.rb +273 -36
- data/lib/cequel/record/configuration_generator.rb +5 -0
- data/lib/cequel/record/data_set_builder.rb +86 -0
- data/lib/cequel/record/dirty.rb +11 -8
- data/lib/cequel/record/errors.rb +38 -4
- data/lib/cequel/record/has_many_association.rb +42 -9
- data/lib/cequel/record/lazy_record_collection.rb +39 -10
- data/lib/cequel/record/mass_assignment.rb +14 -6
- data/lib/cequel/record/persistence.rb +157 -20
- data/lib/cequel/record/properties.rb +147 -24
- data/lib/cequel/record/railtie.rb +15 -2
- data/lib/cequel/record/record_set.rb +504 -75
- data/lib/cequel/record/schema.rb +77 -13
- data/lib/cequel/record/scoped.rb +16 -11
- data/lib/cequel/record/secondary_indexes.rb +42 -6
- data/lib/cequel/record/tasks.rb +2 -1
- data/lib/cequel/record/validations.rb +51 -11
- data/lib/cequel/schema.rb +9 -0
- data/lib/cequel/schema/column.rb +172 -33
- data/lib/cequel/schema/create_table_dsl.rb +62 -31
- data/lib/cequel/schema/keyspace.rb +106 -7
- data/lib/cequel/schema/migration_validator.rb +128 -0
- data/lib/cequel/schema/table.rb +183 -20
- data/lib/cequel/schema/table_property.rb +92 -34
- data/lib/cequel/schema/table_reader.rb +45 -15
- data/lib/cequel/schema/table_synchronizer.rb +101 -43
- data/lib/cequel/schema/table_updater.rb +114 -19
- data/lib/cequel/schema/table_writer.rb +31 -13
- data/lib/cequel/schema/update_table_dsl.rb +71 -40
- data/lib/cequel/type.rb +214 -53
- data/lib/cequel/util.rb +6 -9
- data/lib/cequel/version.rb +2 -1
- data/spec/examples/record/associations_spec.rb +12 -12
- data/spec/examples/record/persistence_spec.rb +5 -5
- data/spec/examples/record/record_set_spec.rb +62 -50
- data/spec/examples/schema/table_synchronizer_spec.rb +37 -11
- data/spec/examples/schema/table_updater_spec.rb +3 -3
- data/spec/examples/spec_helper.rb +2 -11
- data/spec/examples/type_spec.rb +3 -3
- metadata +23 -4
- 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
|
data/lib/cequel/record/dirty.rb
CHANGED
@@ -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
|
data/lib/cequel/record/errors.rb
CHANGED
@@ -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
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
210
|
-
|
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
|