tallty_duck_record 1.0.0

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