active_data 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,96 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ class ReferencesMany < Base
5
+ def apply_changes
6
+ present_keys = target.reject { |t| t.marked_for_destruction? }.map(&reflection.primary_key).compact
7
+ write_source(present_keys)
8
+ true
9
+ end
10
+
11
+ def target= object
12
+ loaded!
13
+ @target = object.to_a
14
+ end
15
+
16
+ def load_target
17
+ source = read_source
18
+ source.present? ? scope(source).to_a : default
19
+ end
20
+
21
+ def default
22
+ unless evar_loaded?
23
+ default = Array.wrap(reflection.default(owner))
24
+ if default.all? { |object| object.is_a?(reflection.klass) }
25
+ default
26
+ elsif default.all? { |object| object.is_a?(Hash) }
27
+ default.map { |attributes| reflection.klass.new(attributes) }
28
+ else
29
+ scope(default).to_a
30
+ end if default.present?
31
+ end || []
32
+ end
33
+
34
+ def read_source
35
+ attribute.read_before_type_cast
36
+ end
37
+
38
+ def write_source value
39
+ attribute.write_value value
40
+ end
41
+
42
+ def reader force_reload = false
43
+ reload if force_reload
44
+ @proxy ||= Collection::Referenced.new self
45
+ end
46
+
47
+ def replace objects
48
+ loaded!
49
+ transaction do
50
+ clear
51
+ append objects
52
+ end
53
+ end
54
+ alias_method :writer, :replace
55
+
56
+ def concat(*objects)
57
+ append objects.flatten
58
+ reader
59
+ end
60
+
61
+ def clear
62
+ attribute.pollute do
63
+ write_source([])
64
+ end
65
+ reload.empty?
66
+ end
67
+
68
+ def scope source = read_source
69
+ reflection.scope.where(reflection.primary_key => source)
70
+ end
71
+
72
+ def identify
73
+ target.map(&reflection.primary_key)
74
+ end
75
+
76
+ private
77
+
78
+ def append objects
79
+ attribute.pollute do
80
+ objects.each do |object|
81
+ next if target.include?(object)
82
+ raise AssociationTypeMismatch.new(reflection.klass, object.class) unless object.is_a?(reflection.klass)
83
+ target.push(object)
84
+ apply_changes!
85
+ end
86
+ end
87
+ target
88
+ end
89
+
90
+ def attribute
91
+ @attribute ||= owner.attribute(reflection.reference_key)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,83 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ class ReferencesOne < Base
5
+ def apply_changes
6
+ if target && !target.marked_for_destruction?
7
+ write_source identify
8
+ else
9
+ write_source nil
10
+ end
11
+ true
12
+ end
13
+
14
+ def target= object
15
+ loaded!
16
+ @target = object
17
+ end
18
+
19
+ def load_target
20
+ source = read_source
21
+ source ? scope(source).first : default
22
+ end
23
+
24
+ def default
25
+ unless evar_loaded?
26
+ default = reflection.default(owner)
27
+ case default
28
+ when reflection.klass
29
+ default
30
+ when Hash
31
+ reflection.klass.new(default)
32
+ else
33
+ scope(default).first
34
+ end if default
35
+ end
36
+ end
37
+
38
+ def read_source
39
+ attribute.read_before_type_cast
40
+ end
41
+
42
+ def write_source value
43
+ attribute.write_value value
44
+ end
45
+
46
+ def reader force_reload = false
47
+ reset if force_reload
48
+ target
49
+ end
50
+
51
+ def replace object
52
+ unless object.nil? || object.is_a?(reflection.klass)
53
+ raise AssociationTypeMismatch.new(reflection.klass, object.class)
54
+ end
55
+
56
+ transaction do
57
+ attribute.pollute do
58
+ self.target = object
59
+ apply_changes!
60
+ end
61
+ end
62
+
63
+ target
64
+ end
65
+ alias_method :writer, :replace
66
+
67
+ def scope source = read_source
68
+ reflection.scope.where(reflection.primary_key => source)
69
+ end
70
+
71
+ def identify
72
+ target.try(reflection.primary_key)
73
+ end
74
+
75
+ private
76
+
77
+ def attribute
78
+ @attribute ||= owner.attribute(reflection.reference_key)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,100 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ module Reflections
5
+ class Base
6
+ READ = ->(reflection, object) { object.read_attribute reflection.name }
7
+ WRITE = ->(reflection, object, value) { object.write_attribute reflection.name, value }
8
+
9
+ attr_reader :name, :options
10
+ # AR compatibility
11
+ attr_accessor :parent_reflection
12
+ delegate :association_class, to: 'self.class'
13
+
14
+ def self.build target, generated_methods, name, options = {}, &block
15
+ if block
16
+ options[:class] = proc do |reflection|
17
+ superclass = reflection.options[:class_name].to_s.presence.try(:constantize)
18
+ klass = Class.new(superclass || Object) do
19
+ include ActiveData::Model
20
+ include ActiveData::Model::Associations
21
+ include ActiveData::Model::Lifecycle
22
+ include ActiveData::Model::Primary
23
+ end
24
+ target.const_set(name.to_s.classify, klass)
25
+ klass.class_eval(&block)
26
+ klass
27
+ end
28
+ end
29
+ generate_methods name, generated_methods
30
+ target.validates_nested name if options.delete(:validate) && target.respond_to?(:validates_nested)
31
+ new(name, options)
32
+ end
33
+
34
+ def self.generate_methods name, target
35
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
36
+ def #{name} force_reload = false
37
+ association(:#{name}).reader(force_reload)
38
+ end
39
+
40
+ def #{name}= value
41
+ association(:#{name}).writer(value)
42
+ end
43
+ RUBY
44
+ end
45
+
46
+ def self.association_class
47
+ @association_class ||= "ActiveData::Model::Associations::#{name.demodulize}".constantize
48
+ end
49
+
50
+ def initialize name, options = {}
51
+ @name, @options = name.to_sym, options
52
+ end
53
+
54
+ def macro
55
+ self.class.name.demodulize.underscore.to_sym
56
+ end
57
+
58
+ def klass
59
+ @klass ||= options[:class] ?
60
+ options[:class].call(self) :
61
+ (options[:class_name].presence || name.to_s.classify).to_s.constantize
62
+ end
63
+
64
+ def belongs_to?
65
+ false
66
+ end
67
+
68
+ def build_association object
69
+ self.class.association_class.new object, self
70
+ end
71
+
72
+ def read_source object
73
+ (options[:read] || READ).call(self, object)
74
+ end
75
+
76
+ def write_source object, value
77
+ (options[:write] || WRITE).call(self, object, value)
78
+ end
79
+
80
+ def default object
81
+ defaultizer = options[:default]
82
+ if defaultizer.is_a?(Proc)
83
+ if defaultizer.arity != 0
84
+ defaultizer.call(object)
85
+ else
86
+ object.instance_exec(&defaultizer)
87
+ end
88
+ else
89
+ defaultizer
90
+ end
91
+ end
92
+
93
+ def inspect
94
+ "#{self.class.name.demodulize}(#{klass})"
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ module Reflections
5
+ class EmbedsMany < Base
6
+ def self.build target, generated_methods, name, options = {}, &block
7
+ if target < ActiveData::Model::Attributes
8
+ target.add_attribute(ActiveData::Model::Attributes::Reflections::Base, name)
9
+ end
10
+ options[:validate] = true unless options.key?(:validate)
11
+ super
12
+ end
13
+
14
+ def collection?
15
+ true
16
+ end
17
+
18
+ def embedded?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ module Reflections
5
+ class EmbedsOne < Base
6
+ def self.build target, generated_methods, name, options = {}, &block
7
+ if target < ActiveData::Model::Attributes
8
+ target.add_attribute(ActiveData::Model::Attributes::Reflections::Base, name)
9
+ end
10
+ options[:validate] = true unless options.key?(:validate)
11
+ super
12
+ end
13
+
14
+ def self.generate_methods name, target
15
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
16
+ def #{name} force_reload = false
17
+ association(:#{name}).reader(force_reload)
18
+ end
19
+
20
+ def #{name}= value
21
+ association(:#{name}).writer(value)
22
+ end
23
+
24
+ def build_#{name} attributes = {}
25
+ association(:#{name}).build(attributes)
26
+ end
27
+
28
+ def create_#{name} attributes = {}
29
+ association(:#{name}).create(attributes)
30
+ end
31
+
32
+ def create_#{name}! attributes = {}
33
+ association(:#{name}).create!(attributes)
34
+ end
35
+ RUBY
36
+ end
37
+
38
+ def collection?
39
+ false
40
+ end
41
+
42
+ def embedded?
43
+ true
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ module Reflections
5
+ class ReferenceReflection < Base
6
+ def self.build target, generated_methods, name, *args, &block
7
+ reflection = new(name, *args)
8
+ generate_methods name, generated_methods
9
+ reflection
10
+ end
11
+
12
+ def initialize name, *args
13
+ @options = args.extract_options!
14
+ @scope_proc = args.first
15
+ @name = name.to_sym
16
+ end
17
+
18
+ def read_source object
19
+ object.read_attribute(reference_key)
20
+ end
21
+
22
+ def write_source object, value
23
+ object.write_attribute(reference_key, value)
24
+ end
25
+
26
+ def primary_key
27
+ @primary_key ||= options[:primary_key].presence.try(:to_sym) || :id
28
+ end
29
+
30
+ def scope
31
+ @scope ||= begin
32
+ scope = klass.unscoped
33
+ scope = scope.instance_exec(&@scope_proc) if @scope_proc
34
+ scope
35
+ end
36
+ end
37
+
38
+ def embedded?
39
+ false
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ module Reflections
5
+ class ReferencesMany < ReferenceReflection
6
+ def self.build target, generated_methods, name, *args, &block
7
+ reflection = super
8
+
9
+ target.add_attribute(
10
+ ActiveData::Model::Attributes::Reflections::ReferenceMany,
11
+ reflection.reference_key, association: name)
12
+
13
+ reflection
14
+ end
15
+
16
+ def collection?
17
+ true
18
+ end
19
+
20
+ def reference_key
21
+ @reference_key ||= options[:reference_key].presence.try(:to_sym) ||
22
+ :"#{name.to_s.singularize}_#{primary_key.to_s.pluralize}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveData
2
+ module Model
3
+ module Associations
4
+ module Reflections
5
+ class ReferencesOne < ReferenceReflection
6
+ def self.build target, generated_methods, name, *args, &block
7
+ reflection = super
8
+
9
+ target.add_attribute(
10
+ ActiveData::Model::Attributes::Reflections::ReferenceOne,
11
+ reflection.reference_key, association: name)
12
+
13
+ reflection
14
+ end
15
+
16
+ def collection?
17
+ false
18
+ end
19
+
20
+ def reference_key
21
+ @reference_key ||= options[:reference_key].presence.try(:to_sym) ||
22
+ :"#{name}_#{primary_key}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end