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