activeentity 0.0.1.beta14 → 0.0.1.beta15

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +4 -4
  4. data/Rakefile +7 -7
  5. data/lib/active_entity.rb +29 -7
  6. data/lib/active_entity/aggregations.rb +2 -1
  7. data/lib/active_entity/associations.rb +46 -24
  8. data/lib/active_entity/associations/{embedded → embeds}/association.rb +2 -2
  9. data/lib/active_entity/associations/{embedded → embeds}/builder/association.rb +1 -1
  10. data/lib/active_entity/associations/{embedded → embeds}/builder/collection_association.rb +1 -1
  11. data/lib/active_entity/associations/{embedded → embeds}/builder/embedded_in.rb +1 -1
  12. data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_many.rb +1 -1
  13. data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_one.rb +1 -1
  14. data/lib/active_entity/associations/{embedded → embeds}/builder/singular_association.rb +1 -1
  15. data/lib/active_entity/associations/{embedded → embeds}/collection_association.rb +1 -1
  16. data/lib/active_entity/associations/{embedded → embeds}/collection_proxy.rb +2 -2
  17. data/lib/active_entity/associations/{embedded → embeds}/embedded_in_association.rb +1 -1
  18. data/lib/active_entity/associations/{embedded → embeds}/embeds_many_association.rb +1 -1
  19. data/lib/active_entity/associations/{embedded → embeds}/embeds_one_association.rb +2 -1
  20. data/lib/active_entity/associations/{embedded → embeds}/singular_association.rb +1 -1
  21. data/lib/active_entity/attribute_assignment.rb +9 -3
  22. data/lib/active_entity/attribute_methods.rb +12 -11
  23. data/lib/active_entity/attribute_methods/before_type_cast.rb +1 -1
  24. data/lib/active_entity/attribute_methods/dirty.rb +13 -0
  25. data/lib/active_entity/attribute_methods/primary_key.rb +1 -1
  26. data/lib/active_entity/attribute_methods/query.rb +11 -4
  27. data/lib/active_entity/attribute_methods/read.rb +1 -3
  28. data/lib/active_entity/attribute_methods/time_zone_conversion.rb +2 -0
  29. data/lib/active_entity/attribute_methods/write.rb +4 -6
  30. data/lib/active_entity/attributes.rb +76 -2
  31. data/lib/active_entity/base.rb +3 -12
  32. data/lib/active_entity/core.rb +97 -39
  33. data/lib/active_entity/define_callbacks.rb +4 -0
  34. data/lib/active_entity/enum.rb +30 -4
  35. data/lib/active_entity/errors.rb +0 -11
  36. data/lib/active_entity/gem_version.rb +1 -1
  37. data/lib/active_entity/inheritance.rb +4 -106
  38. data/lib/active_entity/integration.rb +1 -1
  39. data/lib/active_entity/model_schema.rb +0 -12
  40. data/lib/active_entity/nested_attributes.rb +5 -12
  41. data/lib/active_entity/railtie.rb +61 -1
  42. data/lib/active_entity/readonly_attributes.rb +9 -1
  43. data/lib/active_entity/reflection.rb +22 -19
  44. data/lib/active_entity/serialization.rb +9 -6
  45. data/lib/active_entity/store.rb +51 -2
  46. data/lib/active_entity/type.rb +8 -8
  47. data/lib/active_entity/type/registry.rb +5 -5
  48. data/lib/active_entity/{validate_embedded_association.rb → validate_embeds_association.rb} +6 -6
  49. data/lib/active_entity/validations.rb +2 -6
  50. data/lib/active_entity/validations/associated.rb +1 -1
  51. data/lib/active_entity/validations/{uniqueness_in_embedding.rb → uniqueness_in_embeds.rb} +1 -1
  52. data/lib/active_entity/validations/uniqueness_on_active_record.rb +46 -40
  53. metadata +27 -30
  54. data/lib/active_entity/type/decimal_without_scale.rb +0 -15
  55. data/lib/active_entity/type/hash_lookup_type_map.rb +0 -25
  56. data/lib/active_entity/type/type_map.rb +0 -62
  57. data/lib/tasks/active_entity_tasks.rake +0 -6
@@ -17,7 +17,7 @@ module ActiveEntity
17
17
  def create(macro, name, scope, options, ar_or_ae)
18
18
  reflection_class_for(macro).new(name, scope, options, ar_or_ae)
19
19
 
20
- # TODO: Support bridge to Active Record
20
+ # TODO: Support bridge to Active Entity
21
21
  # reflection = reflection_class_for(macro).new(name, scope, options, ar_or_ae)
22
22
  # options[:through] ? ActiveRecord::ThroughReflection.new(reflection) : reflection
23
23
  end
@@ -33,6 +33,7 @@ module ActiveEntity
33
33
  end
34
34
 
35
35
  private
36
+
36
37
  def reflection_class_for(macro)
37
38
  case macro
38
39
  when :composed_of
@@ -77,21 +78,21 @@ module ActiveEntity
77
78
  #
78
79
  def reflections
79
80
  @__reflections ||= begin
80
- ref = {}
81
+ ref = {}
81
82
 
82
- _reflections.each do |name, reflection|
83
- parent_reflection = reflection.parent_reflection
83
+ _reflections.each do |name, reflection|
84
+ parent_reflection = reflection.parent_reflection
84
85
 
85
- if parent_reflection
86
- parent_name = parent_reflection.name
87
- ref[parent_name.to_s] = parent_reflection
88
- else
89
- ref[name] = reflection
90
- end
91
- end
86
+ if parent_reflection
87
+ parent_name = parent_reflection.name
88
+ ref[parent_name.to_s] = parent_reflection
89
+ else
90
+ ref[name] = reflection
91
+ end
92
+ end
92
93
 
93
- ref
94
- end
94
+ ref
95
+ end
95
96
  end
96
97
 
97
98
  # Returns an array of AssociationReflection objects for all the
@@ -184,6 +185,7 @@ module ActiveEntity
184
185
  end
185
186
 
186
187
  protected
188
+
187
189
  def actual_source_reflection # FIXME: this is a horrible name
188
190
  self
189
191
  end
@@ -242,12 +244,13 @@ module ActiveEntity
242
244
  def ==(other_aggregation)
243
245
  super ||
244
246
  other_aggregation.kind_of?(self.class) &&
245
- name == other_aggregation.name &&
246
- !other_aggregation.options.nil? &&
247
- active_entity == other_aggregation.active_entity
247
+ name == other_aggregation.name &&
248
+ !other_aggregation.options.nil? &&
249
+ active_entity == other_aggregation.active_entity
248
250
  end
249
251
 
250
252
  private
253
+
251
254
  def derive_class_name
252
255
  name.to_s.camelize
253
256
  end
@@ -417,7 +420,7 @@ module ActiveEntity
417
420
  def collection?; true; end
418
421
 
419
422
  def association_class
420
- Associations::Embedded::EmbedsManyAssociation
423
+ Associations::Embeds::EmbedsManyAssociation
421
424
  end
422
425
  end
423
426
 
@@ -427,7 +430,7 @@ module ActiveEntity
427
430
  def embeds_one?; true; end
428
431
 
429
432
  def association_class
430
- Associations::Embedded::EmbedsOneAssociation
433
+ Associations::Embeds::EmbedsOneAssociation
431
434
  end
432
435
  end
433
436
 
@@ -437,7 +440,7 @@ module ActiveEntity
437
440
  def embedded_in?; true; end
438
441
 
439
442
  def association_class
440
- Associations::Embedded::EmbeddedInAssociation
443
+ Associations::Embeds::EmbeddedInAssociation
441
444
  end
442
445
  end
443
446
  end
@@ -10,14 +10,17 @@ module ActiveEntity #:nodoc:
10
10
  self.include_root_in_json = false
11
11
  end
12
12
 
13
- def serializable_hash(include_embedded: true, **options)
14
- if include_embedded
15
- include = Array.wrap(options[:include]).concat(self.class.embedded_association_names)
16
- options[:include] = include
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
- # options[:except] = Array(options[:except]).map(&:to_s)
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
@@ -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 (eg. PostgreSQL +hstore+/+json+, or MySQL 5.7+
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)
@@ -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 = ActiveEntity::Type::Registry.new
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
- # <tt>override: true</tt> will cause your type to be used instead of the native type.
35
- # <tt>override: false</tt> will cause the native type to be used over yours if one exists.
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 ValidateEmbeddedAssociation
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(:add_embedded_associations_validation_callbacks, reflection)
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::Embedded::Builder::Association.extensions << AssociationBuilderExtension
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 add_embedded_associations_validation_callbacks(reflection)
184
- define_embedded_associations_validation_callbacks(reflection)
183
+ def add_embeds_associations_validation_callbacks(reflection)
184
+ define_embeds_associations_validation_callbacks(reflection)
185
185
  end
186
186
 
187
- def define_embedded_associations_validation_callbacks(reflection)
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 ||= default_validation_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/uniqueness_in_embedding"
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 UniquenessInEmbeddingValidator < ActiveModel::EachValidator # :nodoc:
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,68 @@ 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
- @finder_class =
19
- if options[:ar_class_name].present?
20
- options[:ar_class_name].safe_constantize
21
- elsif options[:ar_class].present? && options[:ar_class].is_a?(Class)
22
- options[:ar_class]
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 @finder_class
26
+ unless @klass
28
27
  raise ArgumentError, "Must provide one of option :class_name or :class."
29
28
  end
30
- unless @finder_class < ActiveRecord::Base
31
- raise ArgumentError, "Class must be an Active Record model, but got #{@finder_class}."
29
+ unless @klass < ActiveRecord::Base
30
+ raise ArgumentError, "Class must be an Active Entity model, but got #{@finder_class}."
32
31
  end
33
- if @finder_class.abstract_class?
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
- value = map_enum_attribute(@finder_class, attribute, value)
43
- if @present_only && value.blank?
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(@finder_class, attribute, value)
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
41
+ relation = build_relation(finder_class, attribute, value)
42
+ if record.persisted?
43
+ if finder_class.primary_key
44
+ relation = relation.where.not(finder_class.primary_key => record.id_in_database)
45
+ else
46
+ raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.")
56
47
  end
57
48
  end
58
49
  relation = scope_relation(record, relation)
59
50
  relation = relation.merge(options[:conditions]) if options[:conditions]
60
51
 
61
52
  if relation.exists?
62
- error_options = options.except(:case_sensitive, :scope, :conditions, :ar_class, :ar_class_name, :primary_key_attribute_name)
53
+ error_options = options.except(:case_sensitive, :scope, :conditions)
63
54
  error_options[:value] = value
64
55
 
65
- record.errors.add(attribute, :taken, error_options)
56
+ record.errors.add(attribute, :taken, **error_options)
66
57
  end
67
58
  end
68
59
 
69
60
  private
70
61
 
62
+ # The check for an existing value should be run from a class that
63
+ # isn't abstract. This means working down from the current class
64
+ # (self), to the first non-abstract class. Since classes don't know
65
+ # their subclasses, we have to build the hierarchy between self and
66
+ # the record's class.
67
+ def find_finder_class_for(record)
68
+ class_hierarchy = [record.class]
69
+
70
+ while class_hierarchy.first != @klass
71
+ class_hierarchy.unshift(class_hierarchy.first.superclass)
72
+ end
73
+
74
+ class_hierarchy.detect { |klass| !klass.abstract_class? }
75
+ end
76
+
71
77
  def build_relation(klass, attribute, value)
72
78
  relation = klass.unscoped
73
79
  comparison = relation.bind_attribute(attribute, value) do |attr, bind|
@@ -111,14 +117,14 @@ module ActiveEntity
111
117
  # across the system. Useful for making sure that only one user
112
118
  # can be named "davidhh".
113
119
  #
114
- # class Person < ActiveRecord::Base
120
+ # class Person < ActiveEntity::Base
115
121
  # validates_uniqueness_of :user_name
116
122
  # end
117
123
  #
118
124
  # It can also validate whether the value of the specified attributes are
119
125
  # unique based on a <tt>:scope</tt> parameter:
120
126
  #
121
- # class Person < ActiveRecord::Base
127
+ # class Person < ActiveEntity::Base
122
128
  # validates_uniqueness_of :user_name, scope: :account_id
123
129
  # end
124
130
  #
@@ -126,7 +132,7 @@ module ActiveEntity
126
132
  # teacher can only be on the schedule once per semester for a particular
127
133
  # class.
128
134
  #
129
- # class TeacherSchedule < ActiveRecord::Base
135
+ # class TeacherSchedule < ActiveEntity::Base
130
136
  # validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
131
137
  # end
132
138
  #
@@ -135,7 +141,7 @@ module ActiveEntity
135
141
  # are not being taken into consideration when validating uniqueness
136
142
  # of the title attribute:
137
143
  #
138
- # class Article < ActiveRecord::Base
144
+ # class Article < ActiveEntity::Base
139
145
  # validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
140
146
  # end
141
147
  #
@@ -172,7 +178,7 @@ module ActiveEntity
172
178
  # === Concurrency and integrity
173
179
  #
174
180
  # Using this validation method in conjunction with
175
- # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save]
181
+ # {ActiveEntity::Base#save}[rdoc-ref:Persistence#save]
176
182
  # does not guarantee the absence of duplicate record insertions, because
177
183
  # uniqueness checks on the application level are inherently prone to race
178
184
  # conditions. For example, suppose that two users try to post a Comment at
@@ -212,7 +218,7 @@ module ActiveEntity
212
218
  # the field's uniqueness.
213
219
  #
214
220
  # When the database catches such a duplicate insertion,
215
- # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveRecord::StatementInvalid
221
+ # {ActiveEntity::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveEntity::StatementInvalid
216
222
  # exception. You can either choose to let this error propagate (which
217
223
  # will result in the default Rails exception page being shown), or you
218
224
  # can catch it and restart the transaction (e.g. by telling the user
@@ -220,17 +226,17 @@ module ActiveEntity
220
226
  # This technique is also known as
221
227
  # {optimistic concurrency control}[https://en.wikipedia.org/wiki/Optimistic_concurrency_control].
222
228
  #
223
- # The bundled ActiveRecord::ConnectionAdapters distinguish unique index
229
+ # The bundled ActiveEntity::ConnectionAdapters distinguish unique index
224
230
  # constraint errors from other types of database errors by throwing an
225
- # ActiveRecord::RecordNotUnique exception. For other adapters you will
231
+ # ActiveEntity::RecordNotUnique exception. For other adapters you will
226
232
  # have to parse the (database-specific) exception message to detect such
227
233
  # a case.
228
234
  #
229
- # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
235
+ # The following bundled adapters throw the ActiveEntity::RecordNotUnique exception:
230
236
  #
231
- # * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
232
- # * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
233
- # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
237
+ # * ActiveEntity::ConnectionAdapters::Mysql2Adapter.
238
+ # * ActiveEntity::ConnectionAdapters::SQLite3Adapter.
239
+ # * ActiveEntity::ConnectionAdapters::PostgreSQLAdapter.
234
240
  def validates_uniqueness_on_active_record_of(*attr_names)
235
241
  validates_with UniquenessOnActiveRecordValidator, _merge_attributes(attr_names)
236
242
  end