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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +41 -0
  3. data/README.md +82 -0
  4. data/Rakefile +28 -0
  5. data/lib/core_ext/array_without_blank.rb +46 -0
  6. data/lib/duck_record.rb +65 -0
  7. data/lib/duck_record/associations.rb +130 -0
  8. data/lib/duck_record/associations/association.rb +271 -0
  9. data/lib/duck_record/associations/belongs_to_association.rb +71 -0
  10. data/lib/duck_record/associations/builder/association.rb +127 -0
  11. data/lib/duck_record/associations/builder/belongs_to.rb +44 -0
  12. data/lib/duck_record/associations/builder/collection_association.rb +45 -0
  13. data/lib/duck_record/associations/builder/embeds_many.rb +9 -0
  14. data/lib/duck_record/associations/builder/embeds_one.rb +9 -0
  15. data/lib/duck_record/associations/builder/has_many.rb +11 -0
  16. data/lib/duck_record/associations/builder/has_one.rb +20 -0
  17. data/lib/duck_record/associations/builder/singular_association.rb +33 -0
  18. data/lib/duck_record/associations/collection_association.rb +476 -0
  19. data/lib/duck_record/associations/collection_proxy.rb +1160 -0
  20. data/lib/duck_record/associations/embeds_association.rb +92 -0
  21. data/lib/duck_record/associations/embeds_many_association.rb +203 -0
  22. data/lib/duck_record/associations/embeds_many_proxy.rb +892 -0
  23. data/lib/duck_record/associations/embeds_one_association.rb +48 -0
  24. data/lib/duck_record/associations/foreign_association.rb +11 -0
  25. data/lib/duck_record/associations/has_many_association.rb +17 -0
  26. data/lib/duck_record/associations/has_one_association.rb +39 -0
  27. data/lib/duck_record/associations/singular_association.rb +73 -0
  28. data/lib/duck_record/attribute.rb +213 -0
  29. data/lib/duck_record/attribute/user_provided_default.rb +30 -0
  30. data/lib/duck_record/attribute_assignment.rb +118 -0
  31. data/lib/duck_record/attribute_decorators.rb +89 -0
  32. data/lib/duck_record/attribute_methods.rb +325 -0
  33. data/lib/duck_record/attribute_methods/before_type_cast.rb +76 -0
  34. data/lib/duck_record/attribute_methods/dirty.rb +107 -0
  35. data/lib/duck_record/attribute_methods/read.rb +78 -0
  36. data/lib/duck_record/attribute_methods/serialization.rb +66 -0
  37. data/lib/duck_record/attribute_methods/write.rb +70 -0
  38. data/lib/duck_record/attribute_mutation_tracker.rb +108 -0
  39. data/lib/duck_record/attribute_set.rb +98 -0
  40. data/lib/duck_record/attribute_set/yaml_encoder.rb +41 -0
  41. data/lib/duck_record/attributes.rb +262 -0
  42. data/lib/duck_record/base.rb +300 -0
  43. data/lib/duck_record/callbacks.rb +324 -0
  44. data/lib/duck_record/coders/json.rb +13 -0
  45. data/lib/duck_record/coders/yaml_column.rb +48 -0
  46. data/lib/duck_record/core.rb +262 -0
  47. data/lib/duck_record/define_callbacks.rb +23 -0
  48. data/lib/duck_record/enum.rb +139 -0
  49. data/lib/duck_record/errors.rb +71 -0
  50. data/lib/duck_record/inheritance.rb +130 -0
  51. data/lib/duck_record/locale/en.yml +46 -0
  52. data/lib/duck_record/model_schema.rb +71 -0
  53. data/lib/duck_record/nested_attributes.rb +555 -0
  54. data/lib/duck_record/nested_validate_association.rb +262 -0
  55. data/lib/duck_record/persistence.rb +39 -0
  56. data/lib/duck_record/readonly_attributes.rb +36 -0
  57. data/lib/duck_record/reflection.rb +650 -0
  58. data/lib/duck_record/serialization.rb +26 -0
  59. data/lib/duck_record/translation.rb +22 -0
  60. data/lib/duck_record/type.rb +77 -0
  61. data/lib/duck_record/type/array.rb +36 -0
  62. data/lib/duck_record/type/array_without_blank.rb +36 -0
  63. data/lib/duck_record/type/date.rb +7 -0
  64. data/lib/duck_record/type/date_time.rb +7 -0
  65. data/lib/duck_record/type/decimal_without_scale.rb +13 -0
  66. data/lib/duck_record/type/internal/abstract_json.rb +33 -0
  67. data/lib/duck_record/type/internal/timezone.rb +15 -0
  68. data/lib/duck_record/type/json.rb +6 -0
  69. data/lib/duck_record/type/registry.rb +97 -0
  70. data/lib/duck_record/type/serialized.rb +63 -0
  71. data/lib/duck_record/type/text.rb +9 -0
  72. data/lib/duck_record/type/time.rb +19 -0
  73. data/lib/duck_record/type/unsigned_integer.rb +15 -0
  74. data/lib/duck_record/validations.rb +67 -0
  75. data/lib/duck_record/validations/subset.rb +74 -0
  76. data/lib/duck_record/validations/uniqueness_on_real_record.rb +248 -0
  77. data/lib/duck_record/version.rb +3 -0
  78. data/lib/tasks/acts_as_record_tasks.rake +4 -0
  79. 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,7 @@
1
+ module DuckRecord
2
+ module Type
3
+ class Date < ActiveModel::Type::Date
4
+ include Internal::Timezone
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module DuckRecord
2
+ module Type
3
+ class DateTime < ActiveModel::Type::DateTime
4
+ include Internal::Timezone
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module DuckRecord
2
+ module Type
3
+ class DecimalWithoutScale < ActiveModel::Type::BigInteger # :nodoc:
4
+ def type
5
+ :decimal
6
+ end
7
+
8
+ def type_cast_for_schema(value)
9
+ value.to_s.inspect
10
+ end
11
+ end
12
+ end
13
+ 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,15 @@
1
+ module DuckRecord
2
+ module Type
3
+ module Internal
4
+ module Timezone
5
+ def is_utc?
6
+ DuckRecord::Base.default_timezone == :utc
7
+ end
8
+
9
+ def default_timezone
10
+ DuckRecord::Base.default_timezone
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ module DuckRecord
2
+ module Type
3
+ class JSON < Internal::AbstractJson
4
+ end
5
+ end
6
+ 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