activeentity 0.0.1.beta14 → 0.0.1.beta15

Sign up to get free protection for your applications and to get access to all the features.
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