tallty_duck_record 1.0.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 +7 -0
- data/MIT-LICENSE +41 -0
- data/README.md +82 -0
- data/Rakefile +28 -0
- data/lib/core_ext/array_without_blank.rb +46 -0
- data/lib/duck_record.rb +65 -0
- data/lib/duck_record/associations.rb +130 -0
- data/lib/duck_record/associations/association.rb +271 -0
- data/lib/duck_record/associations/belongs_to_association.rb +71 -0
- data/lib/duck_record/associations/builder/association.rb +127 -0
- data/lib/duck_record/associations/builder/belongs_to.rb +44 -0
- data/lib/duck_record/associations/builder/collection_association.rb +45 -0
- data/lib/duck_record/associations/builder/embeds_many.rb +9 -0
- data/lib/duck_record/associations/builder/embeds_one.rb +9 -0
- data/lib/duck_record/associations/builder/has_many.rb +11 -0
- data/lib/duck_record/associations/builder/has_one.rb +20 -0
- data/lib/duck_record/associations/builder/singular_association.rb +33 -0
- data/lib/duck_record/associations/collection_association.rb +476 -0
- data/lib/duck_record/associations/collection_proxy.rb +1160 -0
- data/lib/duck_record/associations/embeds_association.rb +92 -0
- data/lib/duck_record/associations/embeds_many_association.rb +203 -0
- data/lib/duck_record/associations/embeds_many_proxy.rb +892 -0
- data/lib/duck_record/associations/embeds_one_association.rb +48 -0
- data/lib/duck_record/associations/foreign_association.rb +11 -0
- data/lib/duck_record/associations/has_many_association.rb +17 -0
- data/lib/duck_record/associations/has_one_association.rb +39 -0
- data/lib/duck_record/associations/singular_association.rb +73 -0
- data/lib/duck_record/attribute.rb +213 -0
- data/lib/duck_record/attribute/user_provided_default.rb +30 -0
- data/lib/duck_record/attribute_assignment.rb +118 -0
- data/lib/duck_record/attribute_decorators.rb +89 -0
- data/lib/duck_record/attribute_methods.rb +325 -0
- data/lib/duck_record/attribute_methods/before_type_cast.rb +76 -0
- data/lib/duck_record/attribute_methods/dirty.rb +107 -0
- data/lib/duck_record/attribute_methods/read.rb +78 -0
- data/lib/duck_record/attribute_methods/serialization.rb +66 -0
- data/lib/duck_record/attribute_methods/write.rb +70 -0
- data/lib/duck_record/attribute_mutation_tracker.rb +108 -0
- data/lib/duck_record/attribute_set.rb +98 -0
- data/lib/duck_record/attribute_set/yaml_encoder.rb +41 -0
- data/lib/duck_record/attributes.rb +262 -0
- data/lib/duck_record/base.rb +300 -0
- data/lib/duck_record/callbacks.rb +324 -0
- data/lib/duck_record/coders/json.rb +13 -0
- data/lib/duck_record/coders/yaml_column.rb +48 -0
- data/lib/duck_record/core.rb +262 -0
- data/lib/duck_record/define_callbacks.rb +23 -0
- data/lib/duck_record/enum.rb +139 -0
- data/lib/duck_record/errors.rb +71 -0
- data/lib/duck_record/inheritance.rb +130 -0
- data/lib/duck_record/locale/en.yml +46 -0
- data/lib/duck_record/model_schema.rb +71 -0
- data/lib/duck_record/nested_attributes.rb +555 -0
- data/lib/duck_record/nested_validate_association.rb +262 -0
- data/lib/duck_record/persistence.rb +39 -0
- data/lib/duck_record/readonly_attributes.rb +36 -0
- data/lib/duck_record/reflection.rb +650 -0
- data/lib/duck_record/serialization.rb +26 -0
- data/lib/duck_record/translation.rb +22 -0
- data/lib/duck_record/type.rb +77 -0
- data/lib/duck_record/type/array.rb +36 -0
- data/lib/duck_record/type/array_without_blank.rb +36 -0
- data/lib/duck_record/type/date.rb +7 -0
- data/lib/duck_record/type/date_time.rb +7 -0
- data/lib/duck_record/type/decimal_without_scale.rb +13 -0
- data/lib/duck_record/type/internal/abstract_json.rb +33 -0
- data/lib/duck_record/type/internal/timezone.rb +15 -0
- data/lib/duck_record/type/json.rb +6 -0
- data/lib/duck_record/type/registry.rb +97 -0
- data/lib/duck_record/type/serialized.rb +63 -0
- data/lib/duck_record/type/text.rb +9 -0
- data/lib/duck_record/type/time.rb +19 -0
- data/lib/duck_record/type/unsigned_integer.rb +15 -0
- data/lib/duck_record/validations.rb +67 -0
- data/lib/duck_record/validations/subset.rb +74 -0
- data/lib/duck_record/validations/uniqueness_on_real_record.rb +248 -0
- data/lib/duck_record/version.rb +3 -0
- data/lib/tasks/acts_as_record_tasks.rake +4 -0
- metadata +181 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module DuckRecord #:nodoc:
|
2
|
+
# = Active Record \Serialization
|
3
|
+
module Serialization
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include ActiveModel::Serializers::JSON
|
6
|
+
|
7
|
+
included do
|
8
|
+
self.include_root_in_json = false
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def read_attribute_for_serialization(key)
|
14
|
+
v = send(key)
|
15
|
+
if v.respond_to?(:serializable_hash)
|
16
|
+
v.serializable_hash
|
17
|
+
elsif v.respond_to?(:to_ary)
|
18
|
+
v.to_ary
|
19
|
+
elsif v.respond_to?(:to_hash)
|
20
|
+
v.to_hash
|
21
|
+
else
|
22
|
+
v
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Translation
|
3
|
+
include ActiveModel::Translation
|
4
|
+
|
5
|
+
# Set the lookup ancestors for ActiveModel.
|
6
|
+
def lookup_ancestors #:nodoc:
|
7
|
+
klass = self
|
8
|
+
classes = [klass]
|
9
|
+
return classes if klass == DuckRecord::Base
|
10
|
+
|
11
|
+
while klass != klass.base_class
|
12
|
+
classes << klass = klass.superclass
|
13
|
+
end
|
14
|
+
classes
|
15
|
+
end
|
16
|
+
|
17
|
+
# Set the i18n scope to overwrite ActiveModel.
|
18
|
+
def i18n_scope #:nodoc:
|
19
|
+
:duck_record
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "active_model/type"
|
2
|
+
|
3
|
+
require "duck_record/type/internal/abstract_json"
|
4
|
+
require "duck_record/type/internal/timezone"
|
5
|
+
|
6
|
+
require "duck_record/type/date"
|
7
|
+
require "duck_record/type/date_time"
|
8
|
+
require "duck_record/type/time"
|
9
|
+
require "duck_record/type/json"
|
10
|
+
|
11
|
+
require "duck_record/type/array"
|
12
|
+
require "duck_record/type/array_without_blank"
|
13
|
+
|
14
|
+
require "duck_record/type/unsigned_integer"
|
15
|
+
require "duck_record/type/decimal_without_scale"
|
16
|
+
require "duck_record/type/text"
|
17
|
+
|
18
|
+
require "duck_record/type/serialized"
|
19
|
+
require "duck_record/type/registry"
|
20
|
+
|
21
|
+
module DuckRecord
|
22
|
+
module Type
|
23
|
+
@registry = Registry.new
|
24
|
+
|
25
|
+
class << self
|
26
|
+
attr_accessor :registry # :nodoc:
|
27
|
+
delegate :add_modifier, to: :registry
|
28
|
+
|
29
|
+
# Add a new type to the registry, allowing it to be referenced as a
|
30
|
+
# symbol by {ActiveRecord::Base.attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
|
31
|
+
# If your type is only meant to be used with a specific database adapter, you can
|
32
|
+
# do so by passing <tt>adapter: :postgresql</tt>. If your type has the same
|
33
|
+
# name as a native type for the current adapter, an exception will be
|
34
|
+
# raised unless you specify an +:override+ option. <tt>override: true</tt> will
|
35
|
+
# cause your type to be used instead of the native type. <tt>override:
|
36
|
+
# false</tt> will cause the native type to be used over yours if one exists.
|
37
|
+
def register(type_name, klass = nil, **options, &block)
|
38
|
+
registry.register(type_name, klass, **options, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def lookup(*args, **kwargs) # :nodoc:
|
42
|
+
registry.lookup(*args, **kwargs)
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_value # :nodoc:
|
46
|
+
@default_value ||= Value.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Helpers = ActiveModel::Type::Helpers
|
51
|
+
BigInteger = ActiveModel::Type::BigInteger
|
52
|
+
Binary = ActiveModel::Type::Binary
|
53
|
+
Boolean = ActiveModel::Type::Boolean
|
54
|
+
Decimal = ActiveModel::Type::Decimal
|
55
|
+
Float = ActiveModel::Type::Float
|
56
|
+
Integer = ActiveModel::Type::Integer
|
57
|
+
String = ActiveModel::Type::String
|
58
|
+
Value = ActiveModel::Type::Value
|
59
|
+
|
60
|
+
register(:big_integer, Type::BigInteger, override: false)
|
61
|
+
register(:decimal_without_scale)
|
62
|
+
register(:binary, Type::Binary, override: false)
|
63
|
+
register(:boolean, Type::Boolean, override: false)
|
64
|
+
register(:date, Type::Date, override: false)
|
65
|
+
register(:datetime, Type::DateTime, override: false)
|
66
|
+
register(:decimal, Type::Decimal, override: false)
|
67
|
+
register(:float, Type::Float, override: false)
|
68
|
+
register(:integer, Type::Integer, override: false)
|
69
|
+
register(:string, Type::String, override: false)
|
70
|
+
register(:text, Type::Text, override: false)
|
71
|
+
register(:time, Type::Time, override: false)
|
72
|
+
register(:json, Type::JSON, override: false)
|
73
|
+
|
74
|
+
add_modifier({ array: true }, Type::Array)
|
75
|
+
add_modifier({ array_without_blank: true }, Type::ArrayWithoutBlank)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Type # :nodoc:
|
3
|
+
class Array < ActiveModel::Type::Value # :nodoc:
|
4
|
+
include ActiveModel::Type::Helpers::Mutable
|
5
|
+
|
6
|
+
attr_reader :subtype
|
7
|
+
delegate :type, :user_input_in_time_zone, :limit, to: :subtype
|
8
|
+
|
9
|
+
def initialize(subtype)
|
10
|
+
@subtype = subtype
|
11
|
+
end
|
12
|
+
|
13
|
+
def cast(value)
|
14
|
+
type_cast_array(value, :cast)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
other.is_a?(Array) && subtype == other.subtype
|
19
|
+
end
|
20
|
+
|
21
|
+
def map(value, &block)
|
22
|
+
value.map(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def type_cast_array(value, method)
|
28
|
+
if value.is_a?(::Array)
|
29
|
+
value.map { |item| type_cast_array(item, method) }
|
30
|
+
else
|
31
|
+
@subtype.public_send(method, value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Type # :nodoc:
|
3
|
+
class ArrayWithoutBlank < ActiveModel::Type::Value # :nodoc:
|
4
|
+
include ActiveModel::Type::Helpers::Mutable
|
5
|
+
|
6
|
+
attr_reader :subtype
|
7
|
+
delegate :type, :user_input_in_time_zone, :limit, to: :subtype
|
8
|
+
|
9
|
+
def initialize(subtype)
|
10
|
+
@subtype = subtype
|
11
|
+
end
|
12
|
+
|
13
|
+
def cast(value)
|
14
|
+
type_cast_array(value, :cast)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
other.is_a?(Array) && subtype == other.subtype
|
19
|
+
end
|
20
|
+
|
21
|
+
def map(value, &block)
|
22
|
+
value.map(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def type_cast_array(value, method)
|
28
|
+
if value.is_a?(::Array)
|
29
|
+
::ArrayWithoutBlank.new value.map { |item| type_cast_array(item, method) }
|
30
|
+
else
|
31
|
+
@subtype.public_send(method, value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Type
|
3
|
+
module Internal # :nodoc:
|
4
|
+
class AbstractJson < ActiveModel::Type::Value # :nodoc:
|
5
|
+
include ActiveModel::Type::Helpers::Mutable
|
6
|
+
|
7
|
+
def type
|
8
|
+
:json
|
9
|
+
end
|
10
|
+
|
11
|
+
def deserialize(value)
|
12
|
+
if value.is_a?(::String)
|
13
|
+
::ActiveSupport::JSON.decode(value) rescue nil
|
14
|
+
else
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize(value)
|
20
|
+
if value.nil?
|
21
|
+
nil
|
22
|
+
else
|
23
|
+
::ActiveSupport::JSON.encode(value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def accessor
|
28
|
+
DuckRecord::Store::StringKeyedHashAccessor
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "active_model/type/registry"
|
2
|
+
|
3
|
+
module DuckRecord
|
4
|
+
# :stopdoc:
|
5
|
+
module Type
|
6
|
+
class Registry < ActiveModel::Type::Registry
|
7
|
+
def add_modifier(options, klass)
|
8
|
+
registrations << DecorationRegistration.new(options, klass)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def registration_klass
|
14
|
+
Registration
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_registration(symbol, *args)
|
18
|
+
registrations
|
19
|
+
.select { |registration| registration.matches?(symbol, *args) }
|
20
|
+
.max
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Registration
|
25
|
+
def initialize(name, block, override: nil)
|
26
|
+
@name = name
|
27
|
+
@block = block
|
28
|
+
@override = override
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(_registry, *args, **kwargs)
|
32
|
+
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
|
33
|
+
block.call(*args, **kwargs)
|
34
|
+
else
|
35
|
+
block.call(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def matches?(type_name, *args, **kwargs)
|
40
|
+
type_name == name
|
41
|
+
end
|
42
|
+
|
43
|
+
def <=>(other)
|
44
|
+
priority <=> other.priority
|
45
|
+
end
|
46
|
+
|
47
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
48
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
49
|
+
protected
|
50
|
+
|
51
|
+
attr_reader :name, :block, :override
|
52
|
+
|
53
|
+
def priority
|
54
|
+
result = 0
|
55
|
+
if override
|
56
|
+
result |= 1
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class DecorationRegistration < Registration
|
63
|
+
def initialize(options, klass)
|
64
|
+
@options = options
|
65
|
+
@klass = klass
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(registry, *args, **kwargs)
|
69
|
+
subtype = registry.lookup(*args, **kwargs.except(*options.keys))
|
70
|
+
klass.new(subtype)
|
71
|
+
end
|
72
|
+
|
73
|
+
def matches?(*args, **kwargs)
|
74
|
+
matches_options?(**kwargs)
|
75
|
+
end
|
76
|
+
|
77
|
+
def priority
|
78
|
+
super | 4
|
79
|
+
end
|
80
|
+
|
81
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
82
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
83
|
+
protected
|
84
|
+
|
85
|
+
attr_reader :options, :klass
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def matches_options?(**kwargs)
|
90
|
+
options.all? do |key, value|
|
91
|
+
kwargs[key] == value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
# :startdoc:
|
97
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Type
|
3
|
+
class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
|
4
|
+
include ActiveModel::Type::Helpers::Mutable
|
5
|
+
|
6
|
+
attr_reader :subtype, :coder
|
7
|
+
|
8
|
+
def initialize(subtype, coder)
|
9
|
+
@subtype = subtype
|
10
|
+
@coder = coder
|
11
|
+
super(subtype)
|
12
|
+
end
|
13
|
+
|
14
|
+
def deserialize(value)
|
15
|
+
if default_value?(value)
|
16
|
+
value
|
17
|
+
else
|
18
|
+
coder.load(super)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def serialize(value)
|
23
|
+
return if value.nil?
|
24
|
+
unless default_value?(value)
|
25
|
+
super coder.dump(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
Kernel.instance_method(:inspect).bind(self).call
|
31
|
+
end
|
32
|
+
|
33
|
+
def changed_in_place?(raw_old_value, value)
|
34
|
+
return false if value.nil?
|
35
|
+
raw_new_value = encoded(value)
|
36
|
+
raw_old_value.nil? != raw_new_value.nil? ||
|
37
|
+
subtype.changed_in_place?(raw_old_value, raw_new_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def accessor
|
41
|
+
DuckRecord::Store::IndifferentHashAccessor
|
42
|
+
end
|
43
|
+
|
44
|
+
def assert_valid_value(value)
|
45
|
+
if coder.respond_to?(:assert_valid_value)
|
46
|
+
coder.assert_valid_value(value, action: "serialize")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def default_value?(value)
|
53
|
+
value == coder.load(nil)
|
54
|
+
end
|
55
|
+
|
56
|
+
def encoded(value)
|
57
|
+
unless default_value?(value)
|
58
|
+
coder.dump(value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|