granite-form 0.1.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/.codeclimate.yml +13 -0
- data/.github/workflows/ci.yml +35 -0
- data/.github/workflows/main.yml +29 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.rubocop.yml +64 -0
- data/.rubocop_todo.yml +48 -0
- data/Appraisals +8 -0
- data/CHANGELOG.md +73 -0
- data/Gemfile +8 -0
- data/Guardfile +77 -0
- data/LICENSE +22 -0
- data/README.md +429 -0
- data/Rakefile +6 -0
- data/gemfiles/rails.4.2.gemfile +15 -0
- data/gemfiles/rails.5.0.gemfile +15 -0
- data/gemfiles/rails.5.1.gemfile +15 -0
- data/gemfiles/rails.5.2.gemfile +15 -0
- data/gemfiles/rails.6.0.gemfile +14 -0
- data/gemfiles/rails.6.1.gemfile +14 -0
- data/gemfiles/rails.7.0.gemfile +14 -0
- data/granite-form.gemspec +31 -0
- data/lib/granite/form/active_record/associations.rb +57 -0
- data/lib/granite/form/active_record/nested_attributes.rb +20 -0
- data/lib/granite/form/base.rb +15 -0
- data/lib/granite/form/config.rb +42 -0
- data/lib/granite/form/errors.rb +111 -0
- data/lib/granite/form/extensions.rb +36 -0
- data/lib/granite/form/model/associations/base.rb +97 -0
- data/lib/granite/form/model/associations/collection/embedded.rb +14 -0
- data/lib/granite/form/model/associations/collection/proxy.rb +35 -0
- data/lib/granite/form/model/associations/embeds_any.rb +19 -0
- data/lib/granite/form/model/associations/embeds_many.rb +152 -0
- data/lib/granite/form/model/associations/embeds_one.rb +112 -0
- data/lib/granite/form/model/associations/nested_attributes.rb +215 -0
- data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +33 -0
- data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +68 -0
- data/lib/granite/form/model/associations/persistence_adapters/base.rb +55 -0
- data/lib/granite/form/model/associations/references_any.rb +43 -0
- data/lib/granite/form/model/associations/references_many.rb +113 -0
- data/lib/granite/form/model/associations/references_one.rb +88 -0
- data/lib/granite/form/model/associations/reflections/base.rb +92 -0
- data/lib/granite/form/model/associations/reflections/embeds_any.rb +52 -0
- data/lib/granite/form/model/associations/reflections/embeds_many.rb +17 -0
- data/lib/granite/form/model/associations/reflections/embeds_one.rb +19 -0
- data/lib/granite/form/model/associations/reflections/references_any.rb +65 -0
- data/lib/granite/form/model/associations/reflections/references_many.rb +30 -0
- data/lib/granite/form/model/associations/reflections/references_one.rb +32 -0
- data/lib/granite/form/model/associations/reflections/singular.rb +37 -0
- data/lib/granite/form/model/associations/validations.rb +41 -0
- data/lib/granite/form/model/associations.rb +120 -0
- data/lib/granite/form/model/attributes/attribute.rb +75 -0
- data/lib/granite/form/model/attributes/base.rb +134 -0
- data/lib/granite/form/model/attributes/collection.rb +19 -0
- data/lib/granite/form/model/attributes/dictionary.rb +28 -0
- data/lib/granite/form/model/attributes/localized.rb +44 -0
- data/lib/granite/form/model/attributes/reference_many.rb +21 -0
- data/lib/granite/form/model/attributes/reference_one.rb +52 -0
- data/lib/granite/form/model/attributes/reflections/attribute.rb +61 -0
- data/lib/granite/form/model/attributes/reflections/base.rb +62 -0
- data/lib/granite/form/model/attributes/reflections/collection.rb +12 -0
- data/lib/granite/form/model/attributes/reflections/dictionary.rb +15 -0
- data/lib/granite/form/model/attributes/reflections/localized.rb +45 -0
- data/lib/granite/form/model/attributes/reflections/reference_many.rb +12 -0
- data/lib/granite/form/model/attributes/reflections/reference_one.rb +49 -0
- data/lib/granite/form/model/attributes/reflections/represents.rb +56 -0
- data/lib/granite/form/model/attributes/represents.rb +67 -0
- data/lib/granite/form/model/attributes.rb +204 -0
- data/lib/granite/form/model/callbacks.rb +72 -0
- data/lib/granite/form/model/conventions.rb +40 -0
- data/lib/granite/form/model/dirty.rb +84 -0
- data/lib/granite/form/model/lifecycle.rb +309 -0
- data/lib/granite/form/model/localization.rb +26 -0
- data/lib/granite/form/model/persistence.rb +59 -0
- data/lib/granite/form/model/primary.rb +59 -0
- data/lib/granite/form/model/representation.rb +101 -0
- data/lib/granite/form/model/scopes.rb +118 -0
- data/lib/granite/form/model/validations/associated.rb +22 -0
- data/lib/granite/form/model/validations/nested.rb +56 -0
- data/lib/granite/form/model/validations.rb +29 -0
- data/lib/granite/form/model.rb +33 -0
- data/lib/granite/form/railtie.rb +9 -0
- data/lib/granite/form/undefined_class.rb +11 -0
- data/lib/granite/form/version.rb +5 -0
- data/lib/granite/form.rb +163 -0
- data/spec/lib/granite/form/active_record/associations_spec.rb +211 -0
- data/spec/lib/granite/form/active_record/nested_attributes_spec.rb +15 -0
- data/spec/lib/granite/form/config_spec.rb +66 -0
- data/spec/lib/granite/form/model/associations/embeds_many_spec.rb +706 -0
- data/spec/lib/granite/form/model/associations/embeds_one_spec.rb +533 -0
- data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +119 -0
- data/spec/lib/granite/form/model/associations/persistence_adapters/active_record_spec.rb +58 -0
- data/spec/lib/granite/form/model/associations/references_many_spec.rb +572 -0
- data/spec/lib/granite/form/model/associations/references_one_spec.rb +445 -0
- data/spec/lib/granite/form/model/associations/reflections/embeds_any_spec.rb +42 -0
- data/spec/lib/granite/form/model/associations/reflections/embeds_many_spec.rb +145 -0
- data/spec/lib/granite/form/model/associations/reflections/embeds_one_spec.rb +117 -0
- data/spec/lib/granite/form/model/associations/reflections/references_many_spec.rb +303 -0
- data/spec/lib/granite/form/model/associations/reflections/references_one_spec.rb +287 -0
- data/spec/lib/granite/form/model/associations/validations_spec.rb +137 -0
- data/spec/lib/granite/form/model/associations_spec.rb +198 -0
- data/spec/lib/granite/form/model/attributes/attribute_spec.rb +186 -0
- data/spec/lib/granite/form/model/attributes/base_spec.rb +97 -0
- data/spec/lib/granite/form/model/attributes/collection_spec.rb +72 -0
- data/spec/lib/granite/form/model/attributes/dictionary_spec.rb +100 -0
- data/spec/lib/granite/form/model/attributes/localized_spec.rb +103 -0
- data/spec/lib/granite/form/model/attributes/reflections/attribute_spec.rb +72 -0
- data/spec/lib/granite/form/model/attributes/reflections/base_spec.rb +56 -0
- data/spec/lib/granite/form/model/attributes/reflections/collection_spec.rb +37 -0
- data/spec/lib/granite/form/model/attributes/reflections/dictionary_spec.rb +43 -0
- data/spec/lib/granite/form/model/attributes/reflections/localized_spec.rb +37 -0
- data/spec/lib/granite/form/model/attributes/reflections/represents_spec.rb +70 -0
- data/spec/lib/granite/form/model/attributes/represents_spec.rb +85 -0
- data/spec/lib/granite/form/model/attributes_spec.rb +350 -0
- data/spec/lib/granite/form/model/callbacks_spec.rb +337 -0
- data/spec/lib/granite/form/model/conventions_spec.rb +11 -0
- data/spec/lib/granite/form/model/dirty_spec.rb +84 -0
- data/spec/lib/granite/form/model/lifecycle_spec.rb +356 -0
- data/spec/lib/granite/form/model/persistence_spec.rb +46 -0
- data/spec/lib/granite/form/model/primary_spec.rb +84 -0
- data/spec/lib/granite/form/model/representation_spec.rb +139 -0
- data/spec/lib/granite/form/model/scopes_spec.rb +86 -0
- data/spec/lib/granite/form/model/typecasting_spec.rb +193 -0
- data/spec/lib/granite/form/model/validations/associated_spec.rb +102 -0
- data/spec/lib/granite/form/model/validations/nested_spec.rb +164 -0
- data/spec/lib/granite/form/model/validations_spec.rb +31 -0
- data/spec/lib/granite/form/model_spec.rb +10 -0
- data/spec/lib/granite/form_spec.rb +11 -0
- data/spec/shared/nested_attribute_examples.rb +332 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/support/model_helpers.rb +10 -0
- data/spec/support/muffle_helper.rb +7 -0
- metadata +403 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Associations
|
|
5
|
+
module Reflections
|
|
6
|
+
class Base
|
|
7
|
+
READ = ->(reflection, object) { object.read_attribute reflection.name }
|
|
8
|
+
WRITE = ->(reflection, object, value) { object.write_attribute reflection.name, value }
|
|
9
|
+
|
|
10
|
+
attr_reader :name, :options
|
|
11
|
+
# AR compatibility
|
|
12
|
+
attr_accessor :parent_reflection
|
|
13
|
+
delegate :association_class, to: 'self.class'
|
|
14
|
+
|
|
15
|
+
def self.build(target, generated_methods, name, options = {}, &_block)
|
|
16
|
+
generate_methods name, generated_methods
|
|
17
|
+
if options.delete(:validate) &&
|
|
18
|
+
target.respond_to?(:validates_nested) &&
|
|
19
|
+
!target.validates_nested?(name)
|
|
20
|
+
target.validates_nested name
|
|
21
|
+
end
|
|
22
|
+
new(name, options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.generate_methods(name, target)
|
|
26
|
+
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
27
|
+
def #{name} force_reload = false
|
|
28
|
+
association(:#{name}).reader(force_reload)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def #{name}= value
|
|
32
|
+
association(:#{name}).writer(value)
|
|
33
|
+
end
|
|
34
|
+
RUBY
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.association_class
|
|
38
|
+
@association_class ||= "Granite::Form::Model::Associations::#{name.demodulize}".constantize
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(name, options = {})
|
|
42
|
+
@name = name.to_sym
|
|
43
|
+
@options = options
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def macro
|
|
47
|
+
self.class.name.demodulize.underscore.to_sym
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def klass
|
|
51
|
+
@klass ||= (options[:class_name].presence || name.to_s.classify).to_s.constantize
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# AR compatibility
|
|
55
|
+
def belongs_to?
|
|
56
|
+
false
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_association(object)
|
|
60
|
+
self.class.association_class.new object, self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def read_source(object)
|
|
64
|
+
(options[:read] || READ).call(self, object)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def write_source(object, value)
|
|
68
|
+
(options[:write] || WRITE).call(self, object, value)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def default(object)
|
|
72
|
+
defaultizer = options[:default]
|
|
73
|
+
if defaultizer.is_a?(Proc)
|
|
74
|
+
if defaultizer.arity.nonzero?
|
|
75
|
+
defaultizer.call(object)
|
|
76
|
+
else
|
|
77
|
+
object.instance_exec(&defaultizer)
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
defaultizer
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def collection?
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Associations
|
|
5
|
+
module Reflections
|
|
6
|
+
class EmbedsAny < Base
|
|
7
|
+
class << self
|
|
8
|
+
def build(target, generated_methods, name, options = {}, &block)
|
|
9
|
+
if block
|
|
10
|
+
options[:class] = proc do |reflection|
|
|
11
|
+
superclass = reflection.options[:class_name].to_s.presence.try(:constantize)
|
|
12
|
+
klass = build_class(superclass)
|
|
13
|
+
target.const_set(name.to_s.classify, klass)
|
|
14
|
+
klass.class_eval(&block)
|
|
15
|
+
klass
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private def build_class(superclass)
|
|
22
|
+
Class.new(superclass || Granite::Form.base_class) do
|
|
23
|
+
include Granite::Form::Model
|
|
24
|
+
include Granite::Form::Model::Associations
|
|
25
|
+
include Granite::Form::Model::Lifecycle
|
|
26
|
+
include Granite::Form::Model::Primary
|
|
27
|
+
include Granite::Form.base_concern if Granite::Form.base_concern
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def klass
|
|
33
|
+
@klass ||= if options[:class]
|
|
34
|
+
options[:class].call(self)
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def inspect
|
|
41
|
+
"#{self.class.name.demodulize}(#{klass})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def embedded?
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Associations
|
|
5
|
+
module Reflections
|
|
6
|
+
class EmbedsMany < EmbedsAny
|
|
7
|
+
def self.build(target, generated_methods, name, options = {}, &block)
|
|
8
|
+
target.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, name) if target < Granite::Form::Model::Attributes
|
|
9
|
+
options[:validate] = true unless options.key?(:validate)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Associations
|
|
5
|
+
module Reflections
|
|
6
|
+
class EmbedsOne < EmbedsAny
|
|
7
|
+
include Singular
|
|
8
|
+
|
|
9
|
+
def self.build(target, generated_methods, name, options = {}, &block)
|
|
10
|
+
target.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, name) if target < Granite::Form::Model::Attributes
|
|
11
|
+
options[:validate] = true unless options.key?(:validate)
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Associations
|
|
5
|
+
module Reflections
|
|
6
|
+
class ReferencesAny < Base
|
|
7
|
+
def self.build(_target, generated_methods, name, *args)
|
|
8
|
+
reflection = new(name, *args)
|
|
9
|
+
generate_methods name, generated_methods
|
|
10
|
+
reflection
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.persistence_adapter(klass)
|
|
14
|
+
adapter = klass.granite_persistence_adapter if klass.respond_to?(:granite_persistence_adapter)
|
|
15
|
+
adapter or raise PersistenceAdapterMissing, klass
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
delegate :primary_key, to: :persistence_adapter
|
|
19
|
+
|
|
20
|
+
def initialize(name, *args)
|
|
21
|
+
@options = args.extract_options!
|
|
22
|
+
@scope_proc = args.first
|
|
23
|
+
@name = name.to_sym
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def klass
|
|
27
|
+
@klass ||= if options[:data_source].present?
|
|
28
|
+
options[:data_source].to_s.constantize
|
|
29
|
+
else
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
alias_method :data_source, :klass
|
|
35
|
+
|
|
36
|
+
def persistence_adapter
|
|
37
|
+
@persistence_adapter ||= self.class.persistence_adapter(klass)
|
|
38
|
+
.new(data_source, options[:primary_key], @scope_proc)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def read_source(object)
|
|
42
|
+
object.read_attribute(reference_key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def write_source(object, value)
|
|
46
|
+
object.write_attribute(reference_key, value)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def embedded?
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def inspect
|
|
54
|
+
"#{self.class.name.demodulize}(#{persistence_adapter.data_type})"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def autosave?
|
|
58
|
+
!!options[:autosave]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'granite/form/model/attributes/reflections/reference_many'
|
|
2
|
+
require 'granite/form/model/attributes/reference_many'
|
|
3
|
+
|
|
4
|
+
module Granite
|
|
5
|
+
module Form
|
|
6
|
+
module Model
|
|
7
|
+
module Associations
|
|
8
|
+
module Reflections
|
|
9
|
+
class ReferencesMany < ReferencesAny
|
|
10
|
+
def self.build(target, generated_methods, name, *args, &block)
|
|
11
|
+
reflection = super
|
|
12
|
+
|
|
13
|
+
target.add_attribute(
|
|
14
|
+
Granite::Form::Model::Attributes::Reflections::ReferenceMany,
|
|
15
|
+
reflection.reference_key, association: name
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
reflection
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def reference_key
|
|
22
|
+
@reference_key ||= options[:reference_key].presence.try(:to_sym) ||
|
|
23
|
+
:"#{name.to_s.singularize}_#{primary_key.to_s.pluralize}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'granite/form/model/attributes/reflections/reference_one'
|
|
2
|
+
require 'granite/form/model/attributes/reference_one'
|
|
3
|
+
|
|
4
|
+
module Granite
|
|
5
|
+
module Form
|
|
6
|
+
module Model
|
|
7
|
+
module Associations
|
|
8
|
+
module Reflections
|
|
9
|
+
class ReferencesOne < ReferencesAny
|
|
10
|
+
include Singular
|
|
11
|
+
|
|
12
|
+
def self.build(target, generated_methods, name, *args, &block)
|
|
13
|
+
reflection = super
|
|
14
|
+
|
|
15
|
+
target.add_attribute(
|
|
16
|
+
Granite::Form::Model::Attributes::Reflections::ReferenceOne,
|
|
17
|
+
reflection.reference_key, association: name
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
reflection
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reference_key
|
|
24
|
+
@reference_key ||= options[:reference_key].presence.try(:to_sym) ||
|
|
25
|
+
:"#{name}_#{primary_key}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Associations
|
|
5
|
+
module Reflections
|
|
6
|
+
module Singular
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def generate_methods(name, target)
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
14
|
+
def build_#{name} attributes = {}
|
|
15
|
+
association(:#{name}).build(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_#{name} attributes = {}
|
|
19
|
+
association(:#{name}).create(attributes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_#{name}! attributes = {}
|
|
23
|
+
association(:#{name}).create!(attributes)
|
|
24
|
+
end
|
|
25
|
+
RUBY
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def collection?
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Associations
|
|
5
|
+
module Validations
|
|
6
|
+
def valid_ancestry?
|
|
7
|
+
errors.clear
|
|
8
|
+
validate_nested!
|
|
9
|
+
run_validations!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
alias_method :validate_ancestry, :valid_ancestry?
|
|
13
|
+
|
|
14
|
+
def invalid_ancestry?
|
|
15
|
+
!valid_ancestry?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def validate_ancestry!
|
|
19
|
+
valid_ancestry? || raise_validation_error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def validate_nested!
|
|
25
|
+
association_names.each do |name|
|
|
26
|
+
association = association(name)
|
|
27
|
+
invalid_block = if association.reflection.klass.method_defined?(:invalid_ansestry?)
|
|
28
|
+
->(object) { object.invalid_ansestry? }
|
|
29
|
+
else
|
|
30
|
+
->(object) { object.invalid? }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Granite::Form::Model::Validations::NestedValidator
|
|
34
|
+
.validate_nested(self, name, association.target, &invalid_block)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'granite/form/model/associations/collection/proxy'
|
|
2
|
+
require 'granite/form/model/associations/collection/embedded'
|
|
3
|
+
|
|
4
|
+
require 'granite/form/model/associations/reflections/base'
|
|
5
|
+
require 'granite/form/model/associations/reflections/singular'
|
|
6
|
+
require 'granite/form/model/associations/reflections/embeds_any'
|
|
7
|
+
require 'granite/form/model/associations/reflections/embeds_one'
|
|
8
|
+
require 'granite/form/model/associations/reflections/embeds_many'
|
|
9
|
+
require 'granite/form/model/associations/reflections/references_any'
|
|
10
|
+
require 'granite/form/model/associations/reflections/references_one'
|
|
11
|
+
require 'granite/form/model/associations/reflections/references_many'
|
|
12
|
+
|
|
13
|
+
require 'granite/form/model/associations/base'
|
|
14
|
+
require 'granite/form/model/associations/embeds_any'
|
|
15
|
+
require 'granite/form/model/associations/embeds_one'
|
|
16
|
+
require 'granite/form/model/associations/embeds_many'
|
|
17
|
+
require 'granite/form/model/associations/references_any'
|
|
18
|
+
require 'granite/form/model/associations/references_one'
|
|
19
|
+
require 'granite/form/model/associations/references_many'
|
|
20
|
+
|
|
21
|
+
require 'granite/form/model/associations/nested_attributes'
|
|
22
|
+
require 'granite/form/model/associations/validations'
|
|
23
|
+
|
|
24
|
+
module Granite
|
|
25
|
+
module Form
|
|
26
|
+
module Model
|
|
27
|
+
module Associations
|
|
28
|
+
extend ActiveSupport::Concern
|
|
29
|
+
|
|
30
|
+
included do
|
|
31
|
+
include NestedAttributes
|
|
32
|
+
|
|
33
|
+
class_attribute :_associations, :_association_aliases, instance_reader: false, instance_writer: false
|
|
34
|
+
self._associations = {}
|
|
35
|
+
self._association_aliases = {}
|
|
36
|
+
|
|
37
|
+
delegate :association_names, to: 'self.class'
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
embeds_many: Reflections::EmbedsMany,
|
|
41
|
+
embeds_one: Reflections::EmbedsOne,
|
|
42
|
+
references_one: Reflections::ReferencesOne,
|
|
43
|
+
references_many: Reflections::ReferencesMany
|
|
44
|
+
}.each do |(name, reflection_class)|
|
|
45
|
+
define_singleton_method name do |*args, &block|
|
|
46
|
+
reflection = reflection_class.build self, generated_associations_methods, *args, &block
|
|
47
|
+
self._associations = _associations.merge(reflection.name => reflection)
|
|
48
|
+
reflection
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module ClassMethods
|
|
54
|
+
def reflections
|
|
55
|
+
_associations
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def alias_association(alias_name, association_name)
|
|
59
|
+
reflection = reflect_on_association(association_name)
|
|
60
|
+
raise ArgumentError, "Can't alias undefined association `#{attribute_name}` on #{self}" unless reflection
|
|
61
|
+
reflection.class.generate_methods alias_name, generated_associations_methods
|
|
62
|
+
self._association_aliases = _association_aliases.merge(alias_name.to_sym => reflection.name)
|
|
63
|
+
reflection
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def reflect_on_association(name)
|
|
67
|
+
name = name.to_sym
|
|
68
|
+
_associations[_association_aliases[name] || name]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def association_names
|
|
72
|
+
_associations.keys
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def attributes_for_inspect
|
|
78
|
+
(_associations.map do |name, reflection|
|
|
79
|
+
"#{name}: #{reflection.inspect}"
|
|
80
|
+
end + [super]).join(', ')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def generated_associations_methods
|
|
84
|
+
@generated_associations_methods ||= const_set(:GeneratedAssociationsMethods, Module.new)
|
|
85
|
+
.tap { |proxy| include proxy }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def ==(other)
|
|
90
|
+
super && association_names.all? do |association|
|
|
91
|
+
public_send(association) == other.public_send(association)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
alias_method :eql?, :==
|
|
96
|
+
|
|
97
|
+
def association(name)
|
|
98
|
+
reflection = self.class.reflect_on_association(name)
|
|
99
|
+
return unless reflection
|
|
100
|
+
(@_associations ||= {})[reflection.name] ||= reflection.build_association(self)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def apply_association_changes!
|
|
104
|
+
association_names.all? do |name|
|
|
105
|
+
association(name).apply_changes!
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def attributes_for_inspect
|
|
112
|
+
(association_names.map do |name|
|
|
113
|
+
association = association(name)
|
|
114
|
+
"#{name}: #{association.inspect}"
|
|
115
|
+
end + [super]).join(', ')
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class Attribute < Base
|
|
6
|
+
delegate :defaultizer, :enumerizer, :normalizers, to: :reflection
|
|
7
|
+
|
|
8
|
+
def write(value)
|
|
9
|
+
return if readonly?
|
|
10
|
+
pollute do
|
|
11
|
+
write_value value
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def read
|
|
16
|
+
variable_cache(:value) do
|
|
17
|
+
normalize(enumerize(typecast(read_before_type_cast)))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def read_before_type_cast
|
|
22
|
+
variable_cache(:value_before_type_cast) do
|
|
23
|
+
defaultize(@value_cache)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default
|
|
28
|
+
defaultizer.is_a?(Proc) ? evaluate(&defaultizer) : defaultizer
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def defaultize(value, default_value = nil)
|
|
32
|
+
!defaultizer.nil? && value.nil? ? default_value || default : value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def enum
|
|
36
|
+
source = enumerizer.is_a?(Proc) ? evaluate(&enumerizer) : enumerizer
|
|
37
|
+
|
|
38
|
+
case source
|
|
39
|
+
when Range
|
|
40
|
+
source.to_a
|
|
41
|
+
when Set
|
|
42
|
+
source
|
|
43
|
+
else
|
|
44
|
+
Array.wrap(source)
|
|
45
|
+
end.to_set
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def enumerize(value)
|
|
49
|
+
set = enum if enumerizer
|
|
50
|
+
value if !set || (set.none? || set.include?(value))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def normalize(value)
|
|
54
|
+
if normalizers.none?
|
|
55
|
+
value
|
|
56
|
+
else
|
|
57
|
+
normalizers.inject(value) do |val, normalizer|
|
|
58
|
+
case normalizer
|
|
59
|
+
when Proc
|
|
60
|
+
evaluate(val, &normalizer)
|
|
61
|
+
when Hash
|
|
62
|
+
normalizer.inject(val) do |v, (name, options)|
|
|
63
|
+
Granite::Form.normalizer(name).call(v, options, self)
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
Granite::Form.normalizer(normalizer).call(val, {}, self)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|