activeentity 0.0.1.beta14 → 6.1.0
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/MIT-LICENSE +1 -1
- data/README.md +24 -7
- data/Rakefile +7 -7
- data/lib/active_entity.rb +30 -7
- data/lib/active_entity/aggregations.rb +2 -1
- data/lib/active_entity/associations.rb +46 -24
- data/lib/active_entity/associations/{embedded → embeds}/association.rb +2 -2
- data/lib/active_entity/associations/{embedded → embeds}/builder/association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/collection_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/embedded_in.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_many.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_one.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/builder/singular_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/collection_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/collection_proxy.rb +2 -2
- data/lib/active_entity/associations/{embedded → embeds}/embedded_in_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/embeds_many_association.rb +1 -1
- data/lib/active_entity/associations/{embedded → embeds}/embeds_one_association.rb +2 -1
- data/lib/active_entity/associations/{embedded → embeds}/singular_association.rb +1 -1
- data/lib/active_entity/attribute_assignment.rb +10 -8
- data/lib/active_entity/attribute_methods.rb +52 -61
- data/lib/active_entity/attribute_methods/before_type_cast.rb +6 -6
- data/lib/active_entity/attribute_methods/dirty.rb +13 -0
- data/lib/active_entity/attribute_methods/primary_key.rb +6 -8
- data/lib/active_entity/attribute_methods/query.rb +11 -8
- data/lib/active_entity/attribute_methods/read.rb +10 -13
- data/lib/active_entity/attribute_methods/time_zone_conversion.rb +2 -0
- data/lib/active_entity/attribute_methods/write.rb +16 -25
- data/lib/active_entity/attributes.rb +76 -2
- data/lib/active_entity/base.rb +3 -14
- data/lib/active_entity/{define_callbacks.rb → callbacks.rb} +5 -1
- data/lib/active_entity/core.rb +97 -39
- data/lib/active_entity/enum.rb +30 -4
- data/lib/active_entity/errors.rb +0 -11
- data/lib/active_entity/gem_version.rb +4 -4
- data/lib/active_entity/inheritance.rb +4 -106
- data/lib/active_entity/integration.rb +1 -1
- data/lib/active_entity/model_schema.rb +0 -12
- data/lib/active_entity/nested_attributes.rb +5 -12
- data/lib/active_entity/railtie.rb +61 -1
- data/lib/active_entity/readonly_attributes.rb +9 -1
- data/lib/active_entity/reflection.rb +22 -19
- data/lib/active_entity/serialization.rb +9 -6
- data/lib/active_entity/store.rb +51 -2
- data/lib/active_entity/type.rb +8 -8
- data/lib/active_entity/type/registry.rb +5 -5
- data/lib/active_entity/{validate_embedded_association.rb → validate_embeds_association.rb} +6 -6
- data/lib/active_entity/validations.rb +2 -6
- data/lib/active_entity/validations/associated.rb +1 -1
- data/lib/active_entity/validations/{uniqueness_in_embedding.rb → uniqueness_in_embeds.rb} +1 -1
- data/lib/active_entity/validations/uniqueness_on_active_record.rb +42 -47
- metadata +33 -36
- data/lib/active_entity/type/decimal_without_scale.rb +0 -15
- data/lib/active_entity/type/hash_lookup_type_map.rb +0 -25
- data/lib/active_entity/type/type_map.rb +0 -62
- data/lib/tasks/active_entity_tasks.rake +0 -6
@@ -10,14 +10,17 @@ module ActiveEntity #:nodoc:
|
|
10
10
|
self.include_root_in_json = false
|
11
11
|
end
|
12
12
|
|
13
|
-
def serializable_hash(
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
def serializable_hash(options = nil)
|
14
|
+
options = options ? options.dup : {}
|
15
|
+
|
16
|
+
include_embeds = options.delete :include_embeds
|
17
|
+
if include_embeds
|
18
|
+
includes = Array.wrap(options[:include]).concat(self.class.embeds_association_names)
|
19
|
+
options[:include] ||= []
|
20
|
+
options[:include].concat includes
|
17
21
|
end
|
18
22
|
|
19
|
-
|
20
|
-
# options[:except] |= Array(self.class.inheritance_attribute)
|
23
|
+
options[:except] = Array(options[:except]).map(&:to_s)
|
21
24
|
|
22
25
|
super(options)
|
23
26
|
end
|
data/lib/active_entity/store.rb
CHANGED
@@ -11,13 +11,19 @@ module ActiveEntity
|
|
11
11
|
# of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
|
12
12
|
# already built around just accessing attributes on the model.
|
13
13
|
#
|
14
|
+
# Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+) and
|
15
|
+
# methods to access the changes made during the last save (+saved_change_to_key?+, +saved_change_to_key+ and
|
16
|
+
# +key_before_last_save+).
|
17
|
+
#
|
18
|
+
# NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead.
|
19
|
+
#
|
14
20
|
# Make sure that you declare the database column used for the serialized store as a text, so there's
|
15
21
|
# plenty of room.
|
16
22
|
#
|
17
23
|
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
|
18
24
|
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
|
19
25
|
#
|
20
|
-
# NOTE: If you are using structured database data types (
|
26
|
+
# NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, or MySQL 5.7+
|
21
27
|
# +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
|
22
28
|
# Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
|
23
29
|
# the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
|
@@ -49,6 +55,12 @@ module ActiveEntity
|
|
49
55
|
# u.settings[:country] # => 'Denmark'
|
50
56
|
# u.settings['country'] # => 'Denmark'
|
51
57
|
#
|
58
|
+
# # Dirty tracking
|
59
|
+
# u.color = 'green'
|
60
|
+
# u.color_changed? # => true
|
61
|
+
# u.color_was # => 'black'
|
62
|
+
# u.color_change # => ['black', 'red']
|
63
|
+
#
|
52
64
|
# # Add additional accessors to an existing store through store_accessor
|
53
65
|
# class SuperUser < User
|
54
66
|
# store_accessor :settings, :privileges, :servants
|
@@ -91,7 +103,7 @@ module ActiveEntity
|
|
91
103
|
module ClassMethods
|
92
104
|
def store(store_attribute, options = {})
|
93
105
|
serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
|
94
|
-
store_accessor(store_attribute, options[:accessors], options.slice(:prefix, :suffix)) if options.has_key? :accessors
|
106
|
+
store_accessor(store_attribute, options[:accessors], **options.slice(:prefix, :suffix)) if options.has_key? :accessors
|
95
107
|
end
|
96
108
|
|
97
109
|
def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
|
@@ -127,6 +139,42 @@ module ActiveEntity
|
|
127
139
|
define_method(accessor_key) do
|
128
140
|
read_store_attribute(store_attribute, key)
|
129
141
|
end
|
142
|
+
|
143
|
+
define_method("#{accessor_key}_changed?") do
|
144
|
+
return false unless attribute_changed?(store_attribute)
|
145
|
+
prev_store, new_store = changes[store_attribute]
|
146
|
+
prev_store&.dig(key) != new_store&.dig(key)
|
147
|
+
end
|
148
|
+
|
149
|
+
define_method("#{accessor_key}_change") do
|
150
|
+
return unless attribute_changed?(store_attribute)
|
151
|
+
prev_store, new_store = changes[store_attribute]
|
152
|
+
[prev_store&.dig(key), new_store&.dig(key)]
|
153
|
+
end
|
154
|
+
|
155
|
+
define_method("#{accessor_key}_was") do
|
156
|
+
return unless attribute_changed?(store_attribute)
|
157
|
+
prev_store, _new_store = changes[store_attribute]
|
158
|
+
prev_store&.dig(key)
|
159
|
+
end
|
160
|
+
|
161
|
+
define_method("saved_change_to_#{accessor_key}?") do
|
162
|
+
return false unless saved_change_to_attribute?(store_attribute)
|
163
|
+
prev_store, new_store = saved_change_to_attribute(store_attribute)
|
164
|
+
prev_store&.dig(key) != new_store&.dig(key)
|
165
|
+
end
|
166
|
+
|
167
|
+
define_method("saved_change_to_#{accessor_key}") do
|
168
|
+
return unless saved_change_to_attribute?(store_attribute)
|
169
|
+
prev_store, new_store = saved_change_to_attribute(store_attribute)
|
170
|
+
[prev_store&.dig(key), new_store&.dig(key)]
|
171
|
+
end
|
172
|
+
|
173
|
+
define_method("#{accessor_key}_before_last_save") do
|
174
|
+
return unless saved_change_to_attribute?(store_attribute)
|
175
|
+
prev_store, _new_store = saved_change_to_attribute(store_attribute)
|
176
|
+
prev_store&.dig(key)
|
177
|
+
end
|
130
178
|
end
|
131
179
|
end
|
132
180
|
|
@@ -155,6 +203,7 @@ module ActiveEntity
|
|
155
203
|
end
|
156
204
|
|
157
205
|
private
|
206
|
+
|
158
207
|
def read_store_attribute(store_attribute, key) # :doc:
|
159
208
|
accessor = store_accessor_for(store_attribute)
|
160
209
|
accessor.read(self, store_attribute, key)
|
data/lib/active_entity/type.rb
CHANGED
@@ -6,7 +6,6 @@ require "active_entity/type/internal/timezone"
|
|
6
6
|
|
7
7
|
require "active_entity/type/date"
|
8
8
|
require "active_entity/type/date_time"
|
9
|
-
require "active_entity/type/decimal_without_scale"
|
10
9
|
require "active_entity/type/json"
|
11
10
|
require "active_entity/type/time"
|
12
11
|
require "active_entity/type/text"
|
@@ -18,12 +17,9 @@ require "active_entity/type/modifiers/array_without_blank"
|
|
18
17
|
require "active_entity/type/serialized"
|
19
18
|
require "active_entity/type/registry"
|
20
19
|
|
21
|
-
require "active_entity/type/type_map"
|
22
|
-
require "active_entity/type/hash_lookup_type_map"
|
23
|
-
|
24
20
|
module ActiveEntity
|
25
21
|
module Type
|
26
|
-
@registry =
|
22
|
+
@registry = Registry.new
|
27
23
|
|
28
24
|
class << self
|
29
25
|
attr_accessor :registry # :nodoc:
|
@@ -31,8 +27,12 @@ module ActiveEntity
|
|
31
27
|
|
32
28
|
# Add a new type to the registry, allowing it to be referenced as a
|
33
29
|
# symbol by {ActiveEntity::Base.attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
|
34
|
-
#
|
35
|
-
# <tt>
|
30
|
+
# If your type is only meant to be used with a specific database adapter, you can
|
31
|
+
# do so by passing <tt>adapter: :postgresql</tt>. If your type has the same
|
32
|
+
# name as a native type for the current adapter, an exception will be
|
33
|
+
# raised unless you specify an +:override+ option. <tt>override: true</tt> will
|
34
|
+
# cause your type to be used instead of the native type. <tt>override:
|
35
|
+
# false</tt> will cause the native type to be used over yours if one exists.
|
36
36
|
def register(type_name, klass = nil, **options, &block)
|
37
37
|
registry.register(type_name, klass, **options, &block)
|
38
38
|
end
|
@@ -46,7 +46,6 @@ module ActiveEntity
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
Helpers = ActiveModel::Type::Helpers
|
50
49
|
BigInteger = ActiveModel::Type::BigInteger
|
51
50
|
Binary = ActiveModel::Type::Binary
|
52
51
|
Boolean = ActiveModel::Type::Boolean
|
@@ -67,6 +66,7 @@ module ActiveEntity
|
|
67
66
|
register(:decimal, Type::Decimal, override: false)
|
68
67
|
register(:float, Type::Float, override: false)
|
69
68
|
register(:integer, Type::Integer, override: false)
|
69
|
+
register(:unsigned_integer, Type::UnsignedInteger, override: false)
|
70
70
|
register(:json, Type::Json, override: false)
|
71
71
|
register(:string, Type::String, override: false)
|
72
72
|
register(:text, Type::Text, override: false)
|
@@ -6,8 +6,8 @@ module ActiveEntity
|
|
6
6
|
# :stopdoc:
|
7
7
|
module Type
|
8
8
|
class Registry < ActiveModel::Type::Registry
|
9
|
-
def add_modifier(options, klass)
|
10
|
-
registrations << DecorationRegistration.new(options, klass)
|
9
|
+
def add_modifier(options, klass, **args)
|
10
|
+
registrations << DecorationRegistration.new(options, klass, **args)
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
@@ -16,9 +16,9 @@ module ActiveEntity
|
|
16
16
|
Registration
|
17
17
|
end
|
18
18
|
|
19
|
-
def find_registration(symbol, *args)
|
19
|
+
def find_registration(symbol, *args, **kwargs)
|
20
20
|
registrations
|
21
|
-
.select { |registration| registration.matches?(symbol, *args) }
|
21
|
+
.select { |registration| registration.matches?(symbol, *args, **kwargs) }
|
22
22
|
.max
|
23
23
|
end
|
24
24
|
end
|
@@ -56,7 +56,7 @@ module ActiveEntity
|
|
56
56
|
end
|
57
57
|
|
58
58
|
class DecorationRegistration < Registration
|
59
|
-
def initialize(options, klass)
|
59
|
+
def initialize(options, klass, **)
|
60
60
|
@options = options
|
61
61
|
@klass = klass
|
62
62
|
end
|
@@ -127,12 +127,12 @@ module ActiveEntity
|
|
127
127
|
# Now it _is_ removed from the database:
|
128
128
|
#
|
129
129
|
# Comment.find_by(id: id).nil? # => true
|
130
|
-
module
|
130
|
+
module ValidateEmbedsAssociation
|
131
131
|
extend ActiveSupport::Concern
|
132
132
|
|
133
133
|
module AssociationBuilderExtension #:nodoc:
|
134
134
|
def self.build(model, reflection)
|
135
|
-
model.send(:
|
135
|
+
model.send(:add_embeds_associations_validation_callbacks, reflection)
|
136
136
|
end
|
137
137
|
|
138
138
|
def self.valid_options
|
@@ -141,7 +141,7 @@ module ActiveEntity
|
|
141
141
|
end
|
142
142
|
|
143
143
|
included do
|
144
|
-
Associations::
|
144
|
+
Associations::Embeds::Builder::Association.extensions << AssociationBuilderExtension
|
145
145
|
|
146
146
|
unless respond_to?(:index_nested_attribute_errors)
|
147
147
|
mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
|
@@ -180,11 +180,11 @@ module ActiveEntity
|
|
180
180
|
# the callbacks to get defined multiple times, there are guards that
|
181
181
|
# check if the save or validation methods have already been defined
|
182
182
|
# before actually defining them.
|
183
|
-
def
|
184
|
-
|
183
|
+
def add_embeds_associations_validation_callbacks(reflection)
|
184
|
+
define_embeds_associations_validation_callbacks(reflection)
|
185
185
|
end
|
186
186
|
|
187
|
-
def
|
187
|
+
def define_embeds_associations_validation_callbacks(reflection)
|
188
188
|
validation_method = :"validate_associated_records_for_#{reflection.name}"
|
189
189
|
if reflection.validate? && !method_defined?(validation_method)
|
190
190
|
if reflection.collection?
|
@@ -23,7 +23,7 @@ module ActiveEntity
|
|
23
23
|
# \Validations with no <tt>:on</tt> option will run no matter the context. \Validations with
|
24
24
|
# some <tt>:on</tt> option will only run in the specified context.
|
25
25
|
def valid?(context = nil)
|
26
|
-
context ||=
|
26
|
+
context ||= :default
|
27
27
|
output = super(context)
|
28
28
|
errors.empty? && output
|
29
29
|
end
|
@@ -32,10 +32,6 @@ module ActiveEntity
|
|
32
32
|
|
33
33
|
private
|
34
34
|
|
35
|
-
def default_validation_context
|
36
|
-
:default
|
37
|
-
end
|
38
|
-
|
39
35
|
def perform_validations(options = {})
|
40
36
|
options[:validate] == false || valid?(options[:context])
|
41
37
|
end
|
@@ -47,5 +43,5 @@ require "active_entity/validations/presence"
|
|
47
43
|
require "active_entity/validations/absence"
|
48
44
|
require "active_entity/validations/length"
|
49
45
|
require "active_entity/validations/subset"
|
50
|
-
require "active_entity/validations/
|
46
|
+
require "active_entity/validations/uniqueness_in_embeds"
|
51
47
|
require "active_entity/validations/uniqueness_on_active_record"
|
@@ -5,7 +5,7 @@ module ActiveEntity
|
|
5
5
|
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
|
6
6
|
def validate_each(record, attribute, value)
|
7
7
|
if Array(value).reject { |r| valid_object?(r) }.any?
|
8
|
-
record.errors.add(attribute, :invalid, options.merge(value: value))
|
8
|
+
record.errors.add(attribute, :invalid, **options.merge(value: value))
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveEntity
|
4
4
|
module Validations
|
5
|
-
class
|
5
|
+
class UniquenessInEmbedsValidator < ActiveModel::EachValidator # :nodoc:
|
6
6
|
ERROR_MESSAGE = "`key` option of the configuration hash must be symbol or array of symbols."
|
7
7
|
|
8
8
|
def check_validity!
|
@@ -12,62 +12,61 @@ module ActiveEntity
|
|
12
12
|
raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \
|
13
13
|
"Pass a symbol or an array of symbols instead: `scope: :user_id`"
|
14
14
|
end
|
15
|
-
|
16
15
|
super
|
17
16
|
|
18
|
-
@
|
19
|
-
if options[:
|
20
|
-
options[:
|
21
|
-
elsif options[:
|
22
|
-
options[:
|
17
|
+
@klass =
|
18
|
+
if options[:class].present?
|
19
|
+
options[:class]
|
20
|
+
elsif options[:class_name].present?
|
21
|
+
options[:class_name].safe_constantize
|
23
22
|
else
|
24
23
|
nil
|
25
24
|
end
|
26
25
|
|
27
|
-
unless @
|
26
|
+
unless @klass
|
28
27
|
raise ArgumentError, "Must provide one of option :class_name or :class."
|
29
28
|
end
|
30
|
-
unless @
|
31
|
-
raise ArgumentError, "Class must be an Active
|
29
|
+
unless @klass < ActiveRecord::Base
|
30
|
+
raise ArgumentError, "Class must be an Active Entity model, but got #{@klass}."
|
32
31
|
end
|
33
|
-
if @
|
32
|
+
if @klass.abstract_class?
|
34
33
|
raise ArgumentError, "Class can't be an abstract class."
|
35
34
|
end
|
36
|
-
|
37
|
-
@primary_key_attribute_name = options[:primary_key_attribute_name]
|
38
|
-
@present_only = options[:present_only]
|
39
35
|
end
|
40
36
|
|
41
37
|
def validate_each(record, attribute, value)
|
42
|
-
|
43
|
-
|
44
|
-
return
|
45
|
-
end
|
38
|
+
finder_class = find_finder_class_for(record)
|
39
|
+
value = map_enum_attribute(finder_class, attribute, value)
|
46
40
|
|
47
|
-
relation = build_relation(
|
48
|
-
if @primary_key_attribute_name.present?
|
49
|
-
primary_key_attribute = record.read_attribute(@primary_key_attribute_name)
|
50
|
-
if primary_key_attribute.present?
|
51
|
-
if @finder_class.primary_key
|
52
|
-
relation = relation.where.not(@finder_class.primary_key => primary_key_attribute)
|
53
|
-
else
|
54
|
-
raise ActiveRecord::UnknownPrimaryKey.new(@finder_class, "Can not validate uniqueness for persisted record without primary key.")
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
41
|
+
relation = build_relation(finder_class, attribute, value)
|
58
42
|
relation = scope_relation(record, relation)
|
59
43
|
relation = relation.merge(options[:conditions]) if options[:conditions]
|
60
44
|
|
61
45
|
if relation.exists?
|
62
|
-
error_options = options.except(:case_sensitive, :scope, :conditions
|
46
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
63
47
|
error_options[:value] = value
|
64
48
|
|
65
|
-
record.errors.add(attribute, :taken, error_options)
|
49
|
+
record.errors.add(attribute, :taken, **error_options)
|
66
50
|
end
|
67
51
|
end
|
68
52
|
|
69
53
|
private
|
70
54
|
|
55
|
+
# The check for an existing value should be run from a class that
|
56
|
+
# isn't abstract. This means working down from the current class
|
57
|
+
# (self), to the first non-abstract class. Since classes don't know
|
58
|
+
# their subclasses, we have to build the hierarchy between self and
|
59
|
+
# the record's class.
|
60
|
+
def find_finder_class_for(record)
|
61
|
+
class_hierarchy = [record.class]
|
62
|
+
|
63
|
+
while class_hierarchy.first != @klass
|
64
|
+
class_hierarchy.unshift(class_hierarchy.first.superclass)
|
65
|
+
end
|
66
|
+
|
67
|
+
class_hierarchy.detect { |klass| !klass.abstract_class? }
|
68
|
+
end
|
69
|
+
|
71
70
|
def build_relation(klass, attribute, value)
|
72
71
|
relation = klass.unscoped
|
73
72
|
comparison = relation.bind_attribute(attribute, value) do |attr, bind|
|
@@ -88,11 +87,7 @@ module ActiveEntity
|
|
88
87
|
|
89
88
|
def scope_relation(record, relation)
|
90
89
|
Array(options[:scope]).each do |scope_item|
|
91
|
-
scope_value =
|
92
|
-
record.association(scope_item).reader
|
93
|
-
else
|
94
|
-
record._read_attribute(scope_item)
|
95
|
-
end
|
90
|
+
scope_value = record._read_attribute(scope_item)
|
96
91
|
relation = relation.where(scope_item => scope_value)
|
97
92
|
end
|
98
93
|
|
@@ -111,14 +106,14 @@ module ActiveEntity
|
|
111
106
|
# across the system. Useful for making sure that only one user
|
112
107
|
# can be named "davidhh".
|
113
108
|
#
|
114
|
-
# class Person <
|
109
|
+
# class Person < ActiveEntity::Base
|
115
110
|
# validates_uniqueness_of :user_name
|
116
111
|
# end
|
117
112
|
#
|
118
113
|
# It can also validate whether the value of the specified attributes are
|
119
114
|
# unique based on a <tt>:scope</tt> parameter:
|
120
115
|
#
|
121
|
-
# class Person <
|
116
|
+
# class Person < ActiveEntity::Base
|
122
117
|
# validates_uniqueness_of :user_name, scope: :account_id
|
123
118
|
# end
|
124
119
|
#
|
@@ -126,7 +121,7 @@ module ActiveEntity
|
|
126
121
|
# teacher can only be on the schedule once per semester for a particular
|
127
122
|
# class.
|
128
123
|
#
|
129
|
-
# class TeacherSchedule <
|
124
|
+
# class TeacherSchedule < ActiveEntity::Base
|
130
125
|
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
|
131
126
|
# end
|
132
127
|
#
|
@@ -135,7 +130,7 @@ module ActiveEntity
|
|
135
130
|
# are not being taken into consideration when validating uniqueness
|
136
131
|
# of the title attribute:
|
137
132
|
#
|
138
|
-
# class Article <
|
133
|
+
# class Article < ActiveEntity::Base
|
139
134
|
# validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
|
140
135
|
# end
|
141
136
|
#
|
@@ -172,7 +167,7 @@ module ActiveEntity
|
|
172
167
|
# === Concurrency and integrity
|
173
168
|
#
|
174
169
|
# Using this validation method in conjunction with
|
175
|
-
# {
|
170
|
+
# {ActiveEntity::Base#save}[rdoc-ref:Persistence#save]
|
176
171
|
# does not guarantee the absence of duplicate record insertions, because
|
177
172
|
# uniqueness checks on the application level are inherently prone to race
|
178
173
|
# conditions. For example, suppose that two users try to post a Comment at
|
@@ -212,7 +207,7 @@ module ActiveEntity
|
|
212
207
|
# the field's uniqueness.
|
213
208
|
#
|
214
209
|
# When the database catches such a duplicate insertion,
|
215
|
-
# {
|
210
|
+
# {ActiveEntity::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveEntity::StatementInvalid
|
216
211
|
# exception. You can either choose to let this error propagate (which
|
217
212
|
# will result in the default Rails exception page being shown), or you
|
218
213
|
# can catch it and restart the transaction (e.g. by telling the user
|
@@ -220,17 +215,17 @@ module ActiveEntity
|
|
220
215
|
# This technique is also known as
|
221
216
|
# {optimistic concurrency control}[https://en.wikipedia.org/wiki/Optimistic_concurrency_control].
|
222
217
|
#
|
223
|
-
# The bundled
|
218
|
+
# The bundled ActiveEntity::ConnectionAdapters distinguish unique index
|
224
219
|
# constraint errors from other types of database errors by throwing an
|
225
|
-
#
|
220
|
+
# ActiveEntity::RecordNotUnique exception. For other adapters you will
|
226
221
|
# have to parse the (database-specific) exception message to detect such
|
227
222
|
# a case.
|
228
223
|
#
|
229
|
-
# The following bundled adapters throw the
|
224
|
+
# The following bundled adapters throw the ActiveEntity::RecordNotUnique exception:
|
230
225
|
#
|
231
|
-
# *
|
232
|
-
# *
|
233
|
-
# *
|
226
|
+
# * ActiveEntity::ConnectionAdapters::Mysql2Adapter.
|
227
|
+
# * ActiveEntity::ConnectionAdapters::SQLite3Adapter.
|
228
|
+
# * ActiveEntity::ConnectionAdapters::PostgreSQLAdapter.
|
234
229
|
def validates_uniqueness_on_active_record_of(*attr_names)
|
235
230
|
validates_with UniquenessOnActiveRecordValidator, _merge_attributes(attr_names)
|
236
231
|
end
|