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.
- 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
|