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,134 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :name, :owner
|
|
7
|
+
delegate :type, :typecaster, :readonly, to: :reflection
|
|
8
|
+
|
|
9
|
+
def initialize(name, owner)
|
|
10
|
+
@name = name
|
|
11
|
+
@owner = owner
|
|
12
|
+
@origin = :default
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def reflection
|
|
16
|
+
@owner.class._attributes[name]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def write_value(value, origin: :user)
|
|
20
|
+
reset
|
|
21
|
+
@origin = origin
|
|
22
|
+
@value_cache = value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write(value)
|
|
26
|
+
return if readonly?
|
|
27
|
+
write_value value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reset
|
|
31
|
+
remove_variable(:value, :value_before_type_cast)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def read
|
|
35
|
+
@value_cache
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def read_before_type_cast
|
|
39
|
+
@value_cache
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def came_from_user?
|
|
43
|
+
@origin == :user
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def came_from_default?
|
|
47
|
+
@origin == :default
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def value_present?
|
|
51
|
+
!read.nil? && !(read.respond_to?(:empty?) && read.empty?)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def query
|
|
55
|
+
!(read.respond_to?(:zero?) ? read.zero? : read.blank?)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def typecast(value)
|
|
59
|
+
if value.instance_of?(type)
|
|
60
|
+
value
|
|
61
|
+
else
|
|
62
|
+
typecaster.call(value, self) unless value.nil?
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def readonly?
|
|
67
|
+
!!(readonly.is_a?(Proc) ? evaluate(&readonly) : readonly)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def inspect_attribute
|
|
71
|
+
value = case read
|
|
72
|
+
when Date, Time, DateTime
|
|
73
|
+
%("#{read.to_s(:db)}")
|
|
74
|
+
else
|
|
75
|
+
inspection = read.inspect
|
|
76
|
+
inspection.size > 100 ? inspection.truncate(50) : inspection
|
|
77
|
+
end
|
|
78
|
+
"#{name}: #{value}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def pollute
|
|
82
|
+
pollute = owner.class.dirty? && !owner.send(:attribute_changed?, name)
|
|
83
|
+
|
|
84
|
+
if pollute
|
|
85
|
+
previous_value = owner.__send__(name)
|
|
86
|
+
owner.send("#{name}_will_change!")
|
|
87
|
+
|
|
88
|
+
result = yield
|
|
89
|
+
|
|
90
|
+
owner.__send__(:clear_attribute_changes, [name]) if owner.__send__(name) == previous_value
|
|
91
|
+
|
|
92
|
+
if previous_value != read || (
|
|
93
|
+
read.respond_to?(:changed?) &&
|
|
94
|
+
read.changed?
|
|
95
|
+
)
|
|
96
|
+
owner.send(:set_attribute_was, name, previous_value)
|
|
97
|
+
end
|
|
98
|
+
result
|
|
99
|
+
else
|
|
100
|
+
yield
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def evaluate(*args, &block)
|
|
107
|
+
if block.arity >= 0 && block.arity <= args.length
|
|
108
|
+
owner.instance_exec(*args.first(block.arity), &block)
|
|
109
|
+
else
|
|
110
|
+
args = block.arity < 0 ? args : args.first(block.arity)
|
|
111
|
+
yield(*args, owner)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def remove_variable(*names)
|
|
116
|
+
names.flatten.each do |name|
|
|
117
|
+
name = :"@#{name}"
|
|
118
|
+
remove_instance_variable(name) if instance_variable_defined?(name)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def variable_cache(name)
|
|
123
|
+
name = :"@#{name}"
|
|
124
|
+
if instance_variable_defined?(name)
|
|
125
|
+
instance_variable_get(name)
|
|
126
|
+
else
|
|
127
|
+
instance_variable_set(name, yield)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class Collection < Attribute
|
|
6
|
+
def read
|
|
7
|
+
@value ||= normalize(read_before_type_cast.map do |value|
|
|
8
|
+
enumerize(typecast(value))
|
|
9
|
+
end)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def read_before_type_cast
|
|
13
|
+
@value_before_type_cast ||= Array.wrap(@value_cache).map { |value| defaultize(value) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class Dictionary < Attribute
|
|
6
|
+
delegate :keys, to: :reflection
|
|
7
|
+
|
|
8
|
+
def read
|
|
9
|
+
@value ||= begin
|
|
10
|
+
hash = read_before_type_cast
|
|
11
|
+
hash = hash.stringify_keys.slice(*keys) if keys.present?
|
|
12
|
+
|
|
13
|
+
normalize(Hash[hash.map do |key, value|
|
|
14
|
+
[key, enumerize(typecast(value))]
|
|
15
|
+
end].with_indifferent_access).with_indifferent_access
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def read_before_type_cast
|
|
20
|
+
@value_before_type_cast ||= Hash[(@value_cache.presence || {}).map do |key, value|
|
|
21
|
+
[key, defaultize(value)]
|
|
22
|
+
end].with_indifferent_access
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class Localized < Attribute
|
|
6
|
+
def read
|
|
7
|
+
@value ||= Hash[read_before_type_cast.map do |locale, value|
|
|
8
|
+
[locale.to_s, normalize(enumerize(typecast(value)))]
|
|
9
|
+
end]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def read_before_type_cast
|
|
13
|
+
@value_before_type_cast ||= Hash[(@value_cache.presence || {}).map do |locale, value|
|
|
14
|
+
[locale.to_s, defaultize(value)]
|
|
15
|
+
end]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def write_locale(value, locale)
|
|
19
|
+
pollute do
|
|
20
|
+
write(read.merge(locale.to_s => value))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read_locale(locale)
|
|
25
|
+
read[owner.class.fallbacks(locale).detect do |fallback|
|
|
26
|
+
read[fallback.to_s]
|
|
27
|
+
end.to_s]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def read_locale_before_type_cast(locale)
|
|
31
|
+
read_before_type_cast[owner.class.fallbacks(locale).detect do |fallback|
|
|
32
|
+
read_before_type_cast[fallback.to_s]
|
|
33
|
+
end.to_s]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def locale_query(locale)
|
|
37
|
+
value = read_locale(locale)
|
|
38
|
+
!(value.respond_to?(:zero?) ? value.zero? : value.blank?)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class ReferenceMany < ReferenceOne
|
|
6
|
+
def type_casted_value
|
|
7
|
+
variable_cache(:value) do
|
|
8
|
+
read_before_type_cast.map { |id| typecast(id) }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def read_before_type_cast
|
|
13
|
+
variable_cache(:value_before_type_cast) do
|
|
14
|
+
Array.wrap(@value_cache)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class ReferenceOne < Base
|
|
6
|
+
def write(value)
|
|
7
|
+
pollute do
|
|
8
|
+
previous = type_casted_value
|
|
9
|
+
result = write_value value
|
|
10
|
+
changed = (!value.nil? && type_casted_value.nil?) || type_casted_value != previous
|
|
11
|
+
|
|
12
|
+
association.reset if changed
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def read
|
|
18
|
+
if association.target
|
|
19
|
+
association.identify
|
|
20
|
+
else
|
|
21
|
+
type_casted_value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def type_casted_value
|
|
26
|
+
variable_cache(:value) do
|
|
27
|
+
typecast(read_before_type_cast)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def read_before_type_cast
|
|
32
|
+
@value_cache
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def type
|
|
36
|
+
@type ||= association.reflection.persistence_adapter.primary_key_type
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def typecaster
|
|
40
|
+
@typecaster ||= Granite::Form.typecaster(type.ancestors.grep(Class))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def association
|
|
46
|
+
@association ||= owner.association(reflection.association)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
module Reflections
|
|
6
|
+
class Attribute < Base
|
|
7
|
+
def self.build(target, generated_methods, name, *args, &block)
|
|
8
|
+
attribute = super(target, generated_methods, name, *args, &block)
|
|
9
|
+
generate_methods name, generated_methods
|
|
10
|
+
attribute
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.generate_methods(name, target)
|
|
14
|
+
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
15
|
+
def #{name}
|
|
16
|
+
attribute('#{name}').read
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def #{name}= value
|
|
20
|
+
attribute('#{name}').write(value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def #{name}?
|
|
24
|
+
attribute('#{name}').query
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def #{name}_before_type_cast
|
|
28
|
+
attribute('#{name}').read_before_type_cast
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def #{name}_came_from_user?
|
|
32
|
+
attribute('#{name}').came_from_user?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def #{name}_default
|
|
36
|
+
attribute('#{name}').default
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def #{name}_values
|
|
40
|
+
attribute('#{name}').enum.to_a
|
|
41
|
+
end
|
|
42
|
+
RUBY
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def defaultizer
|
|
46
|
+
@defaultizer ||= options[:default]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def enumerizer
|
|
50
|
+
@enumerizer ||= options[:enum] || options[:in]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def normalizers
|
|
54
|
+
@normalizers ||= Array.wrap(options[:normalize] || options[:normalizer] || options[:normalizers])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
module Reflections
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :name, :options
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def build(_target, _generated_methods, name, *args, &block)
|
|
11
|
+
options = args.extract_options!
|
|
12
|
+
options[:type] = args.first if args.first
|
|
13
|
+
options[:default] = block if block
|
|
14
|
+
new(name, options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def generate_methods(name, target) end
|
|
18
|
+
|
|
19
|
+
def attribute_class
|
|
20
|
+
@attribute_class ||= "Granite::Form::Model::Attributes::#{name.demodulize}".constantize
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(name, options = {})
|
|
25
|
+
@name = name.to_s
|
|
26
|
+
@options = options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_attribute(owner, raw_value = Granite::Form::UNDEFINED)
|
|
30
|
+
attribute = self.class.attribute_class.new(name, owner)
|
|
31
|
+
attribute.write_value(raw_value, origin: :persistence) unless raw_value == Granite::Form::UNDEFINED
|
|
32
|
+
attribute
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def type
|
|
36
|
+
@type ||= case options[:type]
|
|
37
|
+
when Class, Module
|
|
38
|
+
options[:type]
|
|
39
|
+
when nil
|
|
40
|
+
raise "Type is not specified for `#{name}`"
|
|
41
|
+
else
|
|
42
|
+
options[:type].to_s.camelize.constantize
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def typecaster
|
|
47
|
+
@typecaster ||= Granite::Form.typecaster(type.ancestors.grep(Class))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def readonly
|
|
51
|
+
@readonly ||= options[:readonly]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def inspect_reflection
|
|
55
|
+
"#{name}: #{type}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
module Reflections
|
|
6
|
+
class Localized < Attribute
|
|
7
|
+
def self.build(target, generated_methods, name, *args, &block)
|
|
8
|
+
attribute = super(target, generated_methods, name, *args, &block)
|
|
9
|
+
generate_methods name, generated_methods
|
|
10
|
+
attribute
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.generate_methods(name, target)
|
|
14
|
+
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
15
|
+
def #{name}_translations
|
|
16
|
+
attribute('#{name}').read
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def #{name}_translations= value
|
|
20
|
+
attribute('#{name}').write(value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def #{name}
|
|
24
|
+
attribute('#{name}').read_locale(self.class.locale)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def #{name}= value
|
|
28
|
+
attribute('#{name}').write_locale(value, self.class.locale)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def #{name}?
|
|
32
|
+
attribute('#{name}').locale_query(self.class.locale)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def #{name}_before_type_cast
|
|
36
|
+
attribute('#{name}').read_locale_before_type_cast(self.class.locale)
|
|
37
|
+
end
|
|
38
|
+
RUBY
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
module Reflections
|
|
6
|
+
class ReferenceOne < Base
|
|
7
|
+
def self.build(_target, generated_methods, name, *args)
|
|
8
|
+
options = args.extract_options!
|
|
9
|
+
generate_methods name, generated_methods
|
|
10
|
+
new(name, options)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.generate_methods(name, target)
|
|
14
|
+
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
15
|
+
def #{name}
|
|
16
|
+
attribute('#{name}').read
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def #{name}= value
|
|
20
|
+
attribute('#{name}').write(value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def #{name}?
|
|
24
|
+
attribute('#{name}').query
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def #{name}_before_type_cast
|
|
28
|
+
attribute('#{name}').read_before_type_cast
|
|
29
|
+
end
|
|
30
|
+
RUBY
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def type
|
|
34
|
+
Object
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def inspect_reflection
|
|
38
|
+
"#{name}: (reference)"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def association
|
|
42
|
+
@association ||= options[:association].to_s
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
module Reflections
|
|
6
|
+
class Represents < Attribute
|
|
7
|
+
def self.build(target, generated_methods, name, *args, &block)
|
|
8
|
+
options = args.extract_options!
|
|
9
|
+
|
|
10
|
+
reference = target.reflect_on_association(options[:of]) if target.respond_to?(:reflect_on_association)
|
|
11
|
+
reference ||= target.reflect_on_attribute(options[:of]) if target.respond_to?(:reflect_on_attribute)
|
|
12
|
+
options[:of] = reference.name if reference
|
|
13
|
+
validates_nested = target.respond_to?(:validates_nested) && !target.validates_nested?(options[:of])
|
|
14
|
+
target.validates_nested(options[:of]) if validates_nested
|
|
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
|
|
56
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Granite
|
|
2
|
+
module Form
|
|
3
|
+
module Model
|
|
4
|
+
module Attributes
|
|
5
|
+
class Represents < Attribute
|
|
6
|
+
delegate :reader, :reader_before_type_cast, :writer, to: :reflection
|
|
7
|
+
|
|
8
|
+
def write(value)
|
|
9
|
+
return if readonly?
|
|
10
|
+
pollute do
|
|
11
|
+
reset
|
|
12
|
+
reference.send(writer, value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reset
|
|
17
|
+
super
|
|
18
|
+
remove_variable(:cached_value, :cached_value_before_type_cast)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def read
|
|
22
|
+
reset if cached_value != read_value
|
|
23
|
+
variable_cache(:value) do
|
|
24
|
+
normalize(enumerize(defaultize(cached_value, read_before_type_cast)))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def read_before_type_cast
|
|
29
|
+
reset if cached_value_before_type_cast != read_value_before_type_cast
|
|
30
|
+
variable_cache(:value_before_type_cast) do
|
|
31
|
+
defaultize(cached_value_before_type_cast)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def reference
|
|
38
|
+
owner.send(reflection.reference)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def read_value
|
|
42
|
+
ref = reference
|
|
43
|
+
ref.public_send(reader) if ref
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def cached_value
|
|
47
|
+
variable_cache(:cached_value) { read_value }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read_value_before_type_cast
|
|
51
|
+
ref = reference
|
|
52
|
+
return unless ref
|
|
53
|
+
if ref.respond_to?(reader_before_type_cast)
|
|
54
|
+
ref.public_send(reader_before_type_cast)
|
|
55
|
+
else
|
|
56
|
+
ref.public_send(reader)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def cached_value_before_type_cast
|
|
61
|
+
variable_cache(:cached_value_before_type_cast) { read_value_before_type_cast }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|