active_data 0.3.0 → 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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rspec +0 -1
  4. data/.rvmrc +1 -1
  5. data/.travis.yml +13 -6
  6. data/Appraisals +7 -0
  7. data/Gemfile +1 -5
  8. data/Guardfile +68 -15
  9. data/README.md +144 -2
  10. data/active_data.gemspec +19 -11
  11. data/gemfiles/rails.4.0.gemfile +14 -0
  12. data/gemfiles/rails.4.1.gemfile +14 -0
  13. data/gemfiles/rails.4.2.gemfile +14 -0
  14. data/gemfiles/rails.5.0.gemfile +14 -0
  15. data/lib/active_data.rb +120 -3
  16. data/lib/active_data/active_record/associations.rb +50 -0
  17. data/lib/active_data/active_record/nested_attributes.rb +24 -0
  18. data/lib/active_data/config.rb +40 -0
  19. data/lib/active_data/errors.rb +93 -0
  20. data/lib/active_data/extensions.rb +33 -0
  21. data/lib/active_data/model.rb +16 -74
  22. data/lib/active_data/model/associations.rb +84 -15
  23. data/lib/active_data/model/associations/base.rb +79 -0
  24. data/lib/active_data/model/associations/collection/embedded.rb +12 -0
  25. data/lib/active_data/model/associations/collection/proxy.rb +32 -0
  26. data/lib/active_data/model/associations/collection/referenced.rb +26 -0
  27. data/lib/active_data/model/associations/embeds_many.rb +124 -18
  28. data/lib/active_data/model/associations/embeds_one.rb +90 -15
  29. data/lib/active_data/model/associations/nested_attributes.rb +180 -0
  30. data/lib/active_data/model/associations/references_many.rb +96 -0
  31. data/lib/active_data/model/associations/references_one.rb +83 -0
  32. data/lib/active_data/model/associations/reflections/base.rb +100 -0
  33. data/lib/active_data/model/associations/reflections/embeds_many.rb +25 -0
  34. data/lib/active_data/model/associations/reflections/embeds_one.rb +49 -0
  35. data/lib/active_data/model/associations/reflections/reference_reflection.rb +45 -0
  36. data/lib/active_data/model/associations/reflections/references_many.rb +28 -0
  37. data/lib/active_data/model/associations/reflections/references_one.rb +28 -0
  38. data/lib/active_data/model/associations/validations.rb +63 -0
  39. data/lib/active_data/model/attributes.rb +247 -0
  40. data/lib/active_data/model/attributes/attribute.rb +73 -0
  41. data/lib/active_data/model/attributes/base.rb +116 -0
  42. data/lib/active_data/model/attributes/collection.rb +17 -0
  43. data/lib/active_data/model/attributes/dictionary.rb +26 -0
  44. data/lib/active_data/model/attributes/localized.rb +42 -0
  45. data/lib/active_data/model/attributes/reference_many.rb +21 -0
  46. data/lib/active_data/model/attributes/reference_one.rb +42 -0
  47. data/lib/active_data/model/attributes/reflections/attribute.rb +55 -0
  48. data/lib/active_data/model/attributes/reflections/base.rb +62 -0
  49. data/lib/active_data/model/attributes/reflections/collection.rb +10 -0
  50. data/lib/active_data/model/attributes/reflections/dictionary.rb +13 -0
  51. data/lib/active_data/model/attributes/reflections/localized.rb +43 -0
  52. data/lib/active_data/model/attributes/reflections/reference_many.rb +10 -0
  53. data/lib/active_data/model/attributes/reflections/reference_one.rb +58 -0
  54. data/lib/active_data/model/attributes/reflections/represents.rb +55 -0
  55. data/lib/active_data/model/attributes/represents.rb +64 -0
  56. data/lib/active_data/model/callbacks.rb +71 -0
  57. data/lib/active_data/model/conventions.rb +35 -0
  58. data/lib/active_data/model/dirty.rb +77 -0
  59. data/lib/active_data/model/lifecycle.rb +307 -0
  60. data/lib/active_data/model/localization.rb +21 -0
  61. data/lib/active_data/model/persistence.rb +57 -0
  62. data/lib/active_data/model/primary.rb +51 -0
  63. data/lib/active_data/model/scopes.rb +77 -0
  64. data/lib/active_data/model/validations.rb +27 -0
  65. data/lib/active_data/model/validations/associated.rb +19 -0
  66. data/lib/active_data/model/validations/nested.rb +39 -0
  67. data/lib/active_data/railtie.rb +7 -0
  68. data/lib/active_data/version.rb +1 -1
  69. data/spec/lib/active_data/active_record/associations_spec.rb +149 -0
  70. data/spec/lib/active_data/active_record/nested_attributes_spec.rb +16 -0
  71. data/spec/lib/active_data/config_spec.rb +44 -0
  72. data/spec/lib/active_data/model/associations/embeds_many_spec.rb +362 -52
  73. data/spec/lib/active_data/model/associations/embeds_one_spec.rb +250 -31
  74. data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +23 -0
  75. data/spec/lib/active_data/model/associations/references_many_spec.rb +196 -0
  76. data/spec/lib/active_data/model/associations/references_one_spec.rb +134 -0
  77. data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +144 -0
  78. data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +116 -0
  79. data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +255 -0
  80. data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +208 -0
  81. data/spec/lib/active_data/model/associations/validations_spec.rb +153 -0
  82. data/spec/lib/active_data/model/associations_spec.rb +189 -0
  83. data/spec/lib/active_data/model/attributes/attribute_spec.rb +144 -0
  84. data/spec/lib/active_data/model/attributes/base_spec.rb +82 -0
  85. data/spec/lib/active_data/model/attributes/collection_spec.rb +73 -0
  86. data/spec/lib/active_data/model/attributes/dictionary_spec.rb +93 -0
  87. data/spec/lib/active_data/model/attributes/localized_spec.rb +88 -33
  88. data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +72 -0
  89. data/spec/lib/active_data/model/attributes/reflections/base_spec.rb +56 -0
  90. data/spec/lib/active_data/model/attributes/reflections/collection_spec.rb +37 -0
  91. data/spec/lib/active_data/model/attributes/reflections/dictionary_spec.rb +43 -0
  92. data/spec/lib/active_data/model/attributes/reflections/localized_spec.rb +37 -0
  93. data/spec/lib/active_data/model/attributes/reflections/represents_spec.rb +70 -0
  94. data/spec/lib/active_data/model/attributes/represents_spec.rb +153 -0
  95. data/spec/lib/active_data/model/attributes_spec.rb +243 -0
  96. data/spec/lib/active_data/model/callbacks_spec.rb +338 -0
  97. data/spec/lib/active_data/model/conventions_spec.rb +12 -0
  98. data/spec/lib/active_data/model/dirty_spec.rb +75 -0
  99. data/spec/lib/active_data/model/lifecycle_spec.rb +330 -0
  100. data/spec/lib/active_data/model/nested_attributes.rb +202 -0
  101. data/spec/lib/active_data/model/persistence_spec.rb +47 -0
  102. data/spec/lib/active_data/model/primary_spec.rb +84 -0
  103. data/spec/lib/active_data/model/scopes_spec.rb +88 -0
  104. data/spec/lib/active_data/model/typecasting_spec.rb +192 -0
  105. data/spec/lib/active_data/model/validations/associated_spec.rb +94 -0
  106. data/spec/lib/active_data/model/validations/nested_spec.rb +93 -0
  107. data/spec/lib/active_data/model/validations_spec.rb +31 -0
  108. data/spec/lib/active_data/model_spec.rb +1 -32
  109. data/spec/lib/active_data_spec.rb +12 -0
  110. data/spec/spec_helper.rb +39 -0
  111. data/spec/support/model_helpers.rb +10 -0
  112. metadata +246 -54
  113. data/gemfiles/Gemfile.rails-3 +0 -14
  114. data/lib/active_data/attributes/base.rb +0 -69
  115. data/lib/active_data/attributes/localized.rb +0 -42
  116. data/lib/active_data/model/associations/association.rb +0 -30
  117. data/lib/active_data/model/attributable.rb +0 -122
  118. data/lib/active_data/model/collectionizable.rb +0 -55
  119. data/lib/active_data/model/collectionizable/proxy.rb +0 -42
  120. data/lib/active_data/model/extensions.rb +0 -9
  121. data/lib/active_data/model/extensions/array.rb +0 -24
  122. data/lib/active_data/model/extensions/big_decimal.rb +0 -17
  123. data/lib/active_data/model/extensions/boolean.rb +0 -38
  124. data/lib/active_data/model/extensions/date.rb +0 -17
  125. data/lib/active_data/model/extensions/date_time.rb +0 -17
  126. data/lib/active_data/model/extensions/float.rb +0 -17
  127. data/lib/active_data/model/extensions/hash.rb +0 -22
  128. data/lib/active_data/model/extensions/integer.rb +0 -17
  129. data/lib/active_data/model/extensions/localized.rb +0 -22
  130. data/lib/active_data/model/extensions/object.rb +0 -17
  131. data/lib/active_data/model/extensions/string.rb +0 -17
  132. data/lib/active_data/model/extensions/time.rb +0 -17
  133. data/lib/active_data/model/localizable.rb +0 -31
  134. data/lib/active_data/model/nested_attributes.rb +0 -58
  135. data/lib/active_data/model/parameterizable.rb +0 -29
  136. data/lib/active_data/validations.rb +0 -7
  137. data/lib/active_data/validations/associated.rb +0 -17
  138. data/spec/lib/active_data/model/attributable_spec.rb +0 -191
  139. data/spec/lib/active_data/model/collectionizable_spec.rb +0 -60
  140. data/spec/lib/active_data/model/nested_attributes_spec.rb +0 -67
  141. data/spec/lib/active_data/model/type_cast_spec.rb +0 -116
  142. data/spec/lib/active_data/validations/associated_spec.rb +0 -88
@@ -0,0 +1,17 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class Collection < Attribute
5
+ def read
6
+ @value ||= normalize(read_before_type_cast.map do |value|
7
+ enumerize(typecast(value))
8
+ end)
9
+ end
10
+
11
+ def read_before_type_cast
12
+ @value_before_type_cast ||= Array.wrap(@value_cache).map { |value| defaultize(value) }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class Dictionary < Attribute
5
+ delegate :keys, to: :reflection
6
+
7
+ def read
8
+ @value ||= begin
9
+ hash = read_before_type_cast
10
+ hash = hash.stringify_keys.slice(*keys) if keys.present?
11
+
12
+ normalize(Hash[hash.map do |key, value|
13
+ [key, enumerize(typecast(value))]
14
+ end].with_indifferent_access).with_indifferent_access
15
+ end
16
+ end
17
+
18
+ def read_before_type_cast
19
+ @value_before_type_cast ||= Hash[(@value_cache.presence || {}).map do |key, value|
20
+ [key, defaultize(value)]
21
+ end].with_indifferent_access
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class Localized < Attribute
5
+ def read
6
+ @value ||= Hash[read_before_type_cast.map do |locale, value|
7
+ [locale.to_s, normalize(enumerize(typecast(value)))]
8
+ end]
9
+ end
10
+
11
+ def read_before_type_cast
12
+ @value_before_type_cast ||= Hash[(@value_cache.presence || {}).map do |locale, value|
13
+ [locale.to_s, defaultize(value)]
14
+ end]
15
+ end
16
+
17
+ def write_locale value, locale
18
+ pollute do
19
+ write(read.merge(locale.to_s => value))
20
+ end
21
+ end
22
+
23
+ def read_locale locale
24
+ read[owner.class.fallbacks(locale).detect do |fallback|
25
+ read[fallback.to_s]
26
+ end.to_s]
27
+ end
28
+
29
+ def read_locale_before_type_cast locale
30
+ read_before_type_cast[owner.class.fallbacks(locale).detect do |fallback|
31
+ read_before_type_cast[fallback.to_s]
32
+ end.to_s]
33
+ end
34
+
35
+ def locale_query locale
36
+ value = read_locale(locale)
37
+ !(value.respond_to?(:zero?) ? value.zero? : value.blank?)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class ReferenceMany < ReferenceOne
5
+ def read_before_type_cast
6
+ variable_cache(:value_before_type_cast) do
7
+ Array.wrap(@value_cache)
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def type_casted_value
14
+ variable_cache(:value) do
15
+ read_before_type_cast.map { |id| typecast(id) }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,42 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ class ReferenceOne < Base
5
+ def write value
6
+ pollute do
7
+ previous = type_casted_value
8
+ result = write_value value
9
+ if (!value.nil? && type_casted_value.nil?) || type_casted_value != previous
10
+ association.reset
11
+ end
12
+ result
13
+ end
14
+ end
15
+
16
+ def read
17
+ if association.target
18
+ association.identify
19
+ else
20
+ type_casted_value
21
+ end
22
+ end
23
+
24
+ def read_before_type_cast
25
+ @value_cache
26
+ end
27
+
28
+ private
29
+
30
+ def type_casted_value
31
+ variable_cache(:value) do
32
+ typecast(read_before_type_cast)
33
+ end
34
+ end
35
+
36
+ def association
37
+ @association ||= owner.association(reflection.association)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class Attribute < Base
6
+ def self.build target, generated_methods, name, *args, &block
7
+ attribute = super(target, generated_methods, name, *args, &block)
8
+ generate_methods name, generated_methods
9
+ attribute
10
+ end
11
+
12
+ def self.generate_methods name, target
13
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ def #{name}
15
+ attribute('#{name}').read
16
+ end
17
+
18
+ def #{name}= value
19
+ attribute('#{name}').write(value)
20
+ end
21
+
22
+ def #{name}?
23
+ attribute('#{name}').query
24
+ end
25
+
26
+ def #{name}_before_type_cast
27
+ attribute('#{name}').read_before_type_cast
28
+ end
29
+
30
+ def #{name}_default
31
+ attribute('#{name}').default
32
+ end
33
+
34
+ def #{name}_values
35
+ attribute('#{name}').enum.to_a
36
+ end
37
+ RUBY
38
+ end
39
+
40
+ def defaultizer
41
+ @defaultizer ||= options[:default]
42
+ end
43
+
44
+ def enumerizer
45
+ @enumerizer ||= options[:enum] || options[:in]
46
+ end
47
+
48
+ def normalizers
49
+ @normalizers ||= Array.wrap(options[:normalize] || options[:normalizer] || options[:normalizers])
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,62 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class Base
6
+ attr_reader :name, :options
7
+ class << self
8
+ def build target, generated_methods, name, *args, &block
9
+ options = args.extract_options!
10
+ options.merge!(type: args.first) if args.first
11
+ options.merge!(default: block) if block
12
+ new(name, options)
13
+ end
14
+
15
+ def generate_methods name, target
16
+ end
17
+
18
+ def attribute_class
19
+ @attribute_class ||= "ActiveData::Model::Attributes::#{name.demodulize}".constantize
20
+ end
21
+ end
22
+
23
+ def initialize name, options = {}
24
+ @name = name.to_s
25
+ @options = options
26
+ end
27
+
28
+ def build_attribute owner, raw_value = nil
29
+ attribute = self.class.attribute_class.new(name, owner)
30
+ attribute.write_value(raw_value) if raw_value
31
+ attribute
32
+ end
33
+
34
+ def type
35
+ @type ||= case options[:type]
36
+ when Proc
37
+ options[:type].call
38
+ when Class
39
+ options[:type]
40
+ when nil
41
+ raise "Type is not specified for `#{name}`"
42
+ else
43
+ options[:type].to_s.camelize.constantize
44
+ end
45
+ end
46
+
47
+ def typecaster
48
+ @typecaster ||= ActiveData.typecaster(type.ancestors.grep(Class))
49
+ end
50
+
51
+ def readonly
52
+ @readonly ||= options[:readonly]
53
+ end
54
+
55
+ def inspect_reflection
56
+ "#{name}: #{type}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class Collection < Attribute
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class Dictionary < Attribute
6
+ def keys
7
+ @keys ||= Array.wrap(options[:keys]).map(&:to_s)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class Localized < Attribute
6
+ def self.build target, generated_methods, name, *args, &block
7
+ attribute = super(target, generated_methods, name, *args, &block)
8
+ generate_methods name, generated_methods
9
+ attribute
10
+ end
11
+
12
+ def self.generate_methods name, target
13
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ def #{name}_translations
15
+ attribute('#{name}').read
16
+ end
17
+
18
+ def #{name}_translations= value
19
+ attribute('#{name}').write(value)
20
+ end
21
+
22
+ def #{name}
23
+ attribute('#{name}').read_locale(self.class.locale)
24
+ end
25
+
26
+ def #{name}= value
27
+ attribute('#{name}').write_locale(value, self.class.locale)
28
+ end
29
+
30
+ def #{name}?
31
+ attribute('#{name}').locale_query(self.class.locale)
32
+ end
33
+
34
+ def #{name}_before_type_cast
35
+ attribute('#{name}').read_locale_before_type_cast(self.class.locale)
36
+ end
37
+ RUBY
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class ReferenceMany < ReferenceOne
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,58 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class ReferenceOne < Base
6
+ TYPES = {
7
+ integer: Integer,
8
+ float: Float,
9
+ decimal: BigDecimal,
10
+ datetime: Time,
11
+ timestamp: Time,
12
+ time: Time,
13
+ date: Date,
14
+ text: String,
15
+ string: String,
16
+ binary: String,
17
+ boolean: Boolean
18
+ }
19
+
20
+ def self.build target, generated_methods, name, *args, &block
21
+ options = args.extract_options!
22
+ generate_methods name, generated_methods
23
+ type_proc = -> {
24
+ reflection = target.reflect_on_association(options[:association])
25
+ column = reflection.klass.columns_hash[reflection.primary_key.to_s]
26
+ TYPES[column.type]
27
+ }
28
+ new(name, options.reverse_merge(type: type_proc))
29
+ end
30
+
31
+ def self.generate_methods name, target
32
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
33
+ def #{name}
34
+ attribute('#{name}').read
35
+ end
36
+
37
+ def #{name}= value
38
+ attribute('#{name}').write(value)
39
+ end
40
+
41
+ def #{name}?
42
+ attribute('#{name}').query
43
+ end
44
+
45
+ def #{name}_before_type_cast
46
+ attribute('#{name}').read_before_type_cast
47
+ end
48
+ RUBY
49
+ end
50
+
51
+ def association
52
+ @association ||= options[:association].to_s
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,55 @@
1
+ module ActiveData
2
+ module Model
3
+ module Attributes
4
+ module Reflections
5
+ class Represents < Attribute
6
+ def self.build target, generated_methods, name, *args, &block
7
+ options = args.extract_options!
8
+
9
+ reference = target.reflect_on_association(options[:of]) if target.respond_to?(:reflect_on_association)
10
+ reference ||= target.reflect_on_attribute(options[:of]) if target.respond_to?(:reflect_on_attribute)
11
+ if reference
12
+ options[:of] = reference.name
13
+ target.validates_nested options[:of]
14
+ end
15
+
16
+ super(target, generated_methods, name, *args, options, &block)
17
+ end
18
+
19
+ def initialize name, options
20
+ super
21
+ raise ArgumentError, "Undefined reference for `#{name}`" if reference.blank?
22
+ end
23
+
24
+ def type
25
+ Object
26
+ end
27
+
28
+ def reference
29
+ @reference ||= options[:of].to_s
30
+ end
31
+
32
+ def column
33
+ @column ||= options[:column].presence.try(:to_s) || name
34
+ end
35
+
36
+ def reader
37
+ @reader ||= options[:reader].presence.try(:to_s) || column
38
+ end
39
+
40
+ def reader_before_type_cast
41
+ @reader_before_type_cast ||= "#{reader}_before_type_cast"
42
+ end
43
+
44
+ def writer
45
+ @writer ||= "#{options[:writer].presence || column}="
46
+ end
47
+
48
+ def inspect_reflection
49
+ "#{name}: (represents)"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end