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.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +13 -0
  3. data/.github/workflows/ci.yml +35 -0
  4. data/.github/workflows/main.yml +29 -0
  5. data/.gitignore +21 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +64 -0
  8. data/.rubocop_todo.yml +48 -0
  9. data/Appraisals +8 -0
  10. data/CHANGELOG.md +73 -0
  11. data/Gemfile +8 -0
  12. data/Guardfile +77 -0
  13. data/LICENSE +22 -0
  14. data/README.md +429 -0
  15. data/Rakefile +6 -0
  16. data/gemfiles/rails.4.2.gemfile +15 -0
  17. data/gemfiles/rails.5.0.gemfile +15 -0
  18. data/gemfiles/rails.5.1.gemfile +15 -0
  19. data/gemfiles/rails.5.2.gemfile +15 -0
  20. data/gemfiles/rails.6.0.gemfile +14 -0
  21. data/gemfiles/rails.6.1.gemfile +14 -0
  22. data/gemfiles/rails.7.0.gemfile +14 -0
  23. data/granite-form.gemspec +31 -0
  24. data/lib/granite/form/active_record/associations.rb +57 -0
  25. data/lib/granite/form/active_record/nested_attributes.rb +20 -0
  26. data/lib/granite/form/base.rb +15 -0
  27. data/lib/granite/form/config.rb +42 -0
  28. data/lib/granite/form/errors.rb +111 -0
  29. data/lib/granite/form/extensions.rb +36 -0
  30. data/lib/granite/form/model/associations/base.rb +97 -0
  31. data/lib/granite/form/model/associations/collection/embedded.rb +14 -0
  32. data/lib/granite/form/model/associations/collection/proxy.rb +35 -0
  33. data/lib/granite/form/model/associations/embeds_any.rb +19 -0
  34. data/lib/granite/form/model/associations/embeds_many.rb +152 -0
  35. data/lib/granite/form/model/associations/embeds_one.rb +112 -0
  36. data/lib/granite/form/model/associations/nested_attributes.rb +215 -0
  37. data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +33 -0
  38. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +68 -0
  39. data/lib/granite/form/model/associations/persistence_adapters/base.rb +55 -0
  40. data/lib/granite/form/model/associations/references_any.rb +43 -0
  41. data/lib/granite/form/model/associations/references_many.rb +113 -0
  42. data/lib/granite/form/model/associations/references_one.rb +88 -0
  43. data/lib/granite/form/model/associations/reflections/base.rb +92 -0
  44. data/lib/granite/form/model/associations/reflections/embeds_any.rb +52 -0
  45. data/lib/granite/form/model/associations/reflections/embeds_many.rb +17 -0
  46. data/lib/granite/form/model/associations/reflections/embeds_one.rb +19 -0
  47. data/lib/granite/form/model/associations/reflections/references_any.rb +65 -0
  48. data/lib/granite/form/model/associations/reflections/references_many.rb +30 -0
  49. data/lib/granite/form/model/associations/reflections/references_one.rb +32 -0
  50. data/lib/granite/form/model/associations/reflections/singular.rb +37 -0
  51. data/lib/granite/form/model/associations/validations.rb +41 -0
  52. data/lib/granite/form/model/associations.rb +120 -0
  53. data/lib/granite/form/model/attributes/attribute.rb +75 -0
  54. data/lib/granite/form/model/attributes/base.rb +134 -0
  55. data/lib/granite/form/model/attributes/collection.rb +19 -0
  56. data/lib/granite/form/model/attributes/dictionary.rb +28 -0
  57. data/lib/granite/form/model/attributes/localized.rb +44 -0
  58. data/lib/granite/form/model/attributes/reference_many.rb +21 -0
  59. data/lib/granite/form/model/attributes/reference_one.rb +52 -0
  60. data/lib/granite/form/model/attributes/reflections/attribute.rb +61 -0
  61. data/lib/granite/form/model/attributes/reflections/base.rb +62 -0
  62. data/lib/granite/form/model/attributes/reflections/collection.rb +12 -0
  63. data/lib/granite/form/model/attributes/reflections/dictionary.rb +15 -0
  64. data/lib/granite/form/model/attributes/reflections/localized.rb +45 -0
  65. data/lib/granite/form/model/attributes/reflections/reference_many.rb +12 -0
  66. data/lib/granite/form/model/attributes/reflections/reference_one.rb +49 -0
  67. data/lib/granite/form/model/attributes/reflections/represents.rb +56 -0
  68. data/lib/granite/form/model/attributes/represents.rb +67 -0
  69. data/lib/granite/form/model/attributes.rb +204 -0
  70. data/lib/granite/form/model/callbacks.rb +72 -0
  71. data/lib/granite/form/model/conventions.rb +40 -0
  72. data/lib/granite/form/model/dirty.rb +84 -0
  73. data/lib/granite/form/model/lifecycle.rb +309 -0
  74. data/lib/granite/form/model/localization.rb +26 -0
  75. data/lib/granite/form/model/persistence.rb +59 -0
  76. data/lib/granite/form/model/primary.rb +59 -0
  77. data/lib/granite/form/model/representation.rb +101 -0
  78. data/lib/granite/form/model/scopes.rb +118 -0
  79. data/lib/granite/form/model/validations/associated.rb +22 -0
  80. data/lib/granite/form/model/validations/nested.rb +56 -0
  81. data/lib/granite/form/model/validations.rb +29 -0
  82. data/lib/granite/form/model.rb +33 -0
  83. data/lib/granite/form/railtie.rb +9 -0
  84. data/lib/granite/form/undefined_class.rb +11 -0
  85. data/lib/granite/form/version.rb +5 -0
  86. data/lib/granite/form.rb +163 -0
  87. data/spec/lib/granite/form/active_record/associations_spec.rb +211 -0
  88. data/spec/lib/granite/form/active_record/nested_attributes_spec.rb +15 -0
  89. data/spec/lib/granite/form/config_spec.rb +66 -0
  90. data/spec/lib/granite/form/model/associations/embeds_many_spec.rb +706 -0
  91. data/spec/lib/granite/form/model/associations/embeds_one_spec.rb +533 -0
  92. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +119 -0
  93. data/spec/lib/granite/form/model/associations/persistence_adapters/active_record_spec.rb +58 -0
  94. data/spec/lib/granite/form/model/associations/references_many_spec.rb +572 -0
  95. data/spec/lib/granite/form/model/associations/references_one_spec.rb +445 -0
  96. data/spec/lib/granite/form/model/associations/reflections/embeds_any_spec.rb +42 -0
  97. data/spec/lib/granite/form/model/associations/reflections/embeds_many_spec.rb +145 -0
  98. data/spec/lib/granite/form/model/associations/reflections/embeds_one_spec.rb +117 -0
  99. data/spec/lib/granite/form/model/associations/reflections/references_many_spec.rb +303 -0
  100. data/spec/lib/granite/form/model/associations/reflections/references_one_spec.rb +287 -0
  101. data/spec/lib/granite/form/model/associations/validations_spec.rb +137 -0
  102. data/spec/lib/granite/form/model/associations_spec.rb +198 -0
  103. data/spec/lib/granite/form/model/attributes/attribute_spec.rb +186 -0
  104. data/spec/lib/granite/form/model/attributes/base_spec.rb +97 -0
  105. data/spec/lib/granite/form/model/attributes/collection_spec.rb +72 -0
  106. data/spec/lib/granite/form/model/attributes/dictionary_spec.rb +100 -0
  107. data/spec/lib/granite/form/model/attributes/localized_spec.rb +103 -0
  108. data/spec/lib/granite/form/model/attributes/reflections/attribute_spec.rb +72 -0
  109. data/spec/lib/granite/form/model/attributes/reflections/base_spec.rb +56 -0
  110. data/spec/lib/granite/form/model/attributes/reflections/collection_spec.rb +37 -0
  111. data/spec/lib/granite/form/model/attributes/reflections/dictionary_spec.rb +43 -0
  112. data/spec/lib/granite/form/model/attributes/reflections/localized_spec.rb +37 -0
  113. data/spec/lib/granite/form/model/attributes/reflections/represents_spec.rb +70 -0
  114. data/spec/lib/granite/form/model/attributes/represents_spec.rb +85 -0
  115. data/spec/lib/granite/form/model/attributes_spec.rb +350 -0
  116. data/spec/lib/granite/form/model/callbacks_spec.rb +337 -0
  117. data/spec/lib/granite/form/model/conventions_spec.rb +11 -0
  118. data/spec/lib/granite/form/model/dirty_spec.rb +84 -0
  119. data/spec/lib/granite/form/model/lifecycle_spec.rb +356 -0
  120. data/spec/lib/granite/form/model/persistence_spec.rb +46 -0
  121. data/spec/lib/granite/form/model/primary_spec.rb +84 -0
  122. data/spec/lib/granite/form/model/representation_spec.rb +139 -0
  123. data/spec/lib/granite/form/model/scopes_spec.rb +86 -0
  124. data/spec/lib/granite/form/model/typecasting_spec.rb +193 -0
  125. data/spec/lib/granite/form/model/validations/associated_spec.rb +102 -0
  126. data/spec/lib/granite/form/model/validations/nested_spec.rb +164 -0
  127. data/spec/lib/granite/form/model/validations_spec.rb +31 -0
  128. data/spec/lib/granite/form/model_spec.rb +10 -0
  129. data/spec/lib/granite/form_spec.rb +11 -0
  130. data/spec/shared/nested_attribute_examples.rb +332 -0
  131. data/spec/spec_helper.rb +50 -0
  132. data/spec/support/model_helpers.rb +10 -0
  133. data/spec/support/muffle_helper.rb +7 -0
  134. metadata +403 -0
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Base do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+ expect(Target).not_to be_method_defined(:field)
14
+ end
15
+ specify { expect(described_class.build(Class.new, Target, :field).name).to eq('field') }
16
+ end
17
+
18
+ describe '.attribute_class' do
19
+ before do
20
+ stub_class('SomeScope::Borogoves', described_class)
21
+ stub_class('Granite::Form::Model::Attributes::Borogoves')
22
+ end
23
+
24
+ specify { expect(described_class.attribute_class).to eq(Granite::Form::Model::Attributes::Base) }
25
+ specify { expect(SomeScope::Borogoves.attribute_class).to eq(Granite::Form::Model::Attributes::Borogoves) }
26
+ end
27
+
28
+ describe '#name' do
29
+ specify { expect(reflection.name).to eq('field') }
30
+ end
31
+
32
+ describe '#build_attribute' do
33
+ before do
34
+ stub_class('SomeScope::Borogoves', described_class)
35
+ stub_class('Granite::Form::Model::Attributes::Borogoves', Granite::Form::Model::Attributes::Base)
36
+ stub_class(:owner)
37
+ end
38
+
39
+ let(:reflection) { SomeScope::Borogoves.new(:field) }
40
+ let(:owner) { Owner.new }
41
+
42
+ specify { expect(reflection.build_attribute(owner, nil)).to be_a(Granite::Form::Model::Attributes::Borogoves) }
43
+ specify { expect(reflection.build_attribute(owner, nil).name).to eq('field') }
44
+ specify { expect(reflection.build_attribute(owner, nil).owner).to eq(owner) }
45
+ end
46
+
47
+ describe '#type' do
48
+ before { stub_class(:dummy, String) }
49
+
50
+ specify { expect { reflection.type }.to raise_error('Type is not specified for `field`') }
51
+ specify { expect(reflection(type: String).type).to eq(String) }
52
+ specify { expect(reflection(type: :string).type).to eq(String) }
53
+ specify { expect(reflection(type: Dummy).type).to eq(Dummy) }
54
+ specify { expect { reflection(type: :blabla).type }.to raise_error NameError }
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Collection do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+
14
+ expect(Target).to be_method_defined(:field)
15
+ expect(Target).to be_method_defined(:field=)
16
+ expect(Target).to be_method_defined(:field?)
17
+ expect(Target).to be_method_defined(:field_before_type_cast)
18
+ expect(Target).to be_method_defined(:field_default)
19
+ expect(Target).to be_method_defined(:field_values)
20
+ end
21
+ end
22
+
23
+ describe '#generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias)
30
+ expect(Target).to be_method_defined(:field_alias=)
31
+ expect(Target).to be_method_defined(:field_alias?)
32
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
33
+ expect(Target).to be_method_defined(:field_alias_default)
34
+ expect(Target).to be_method_defined(:field_alias_values)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Dictionary do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+
14
+ expect(Target).to be_method_defined(:field)
15
+ expect(Target).to be_method_defined(:field=)
16
+ expect(Target).to be_method_defined(:field?)
17
+ expect(Target).to be_method_defined(:field_before_type_cast)
18
+ expect(Target).to be_method_defined(:field_default)
19
+ expect(Target).to be_method_defined(:field_values)
20
+ end
21
+ end
22
+
23
+ describe '#generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias)
30
+ expect(Target).to be_method_defined(:field_alias=)
31
+ expect(Target).to be_method_defined(:field_alias?)
32
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
33
+ expect(Target).to be_method_defined(:field_alias_default)
34
+ expect(Target).to be_method_defined(:field_alias_values)
35
+ end
36
+ end
37
+
38
+ describe '#keys' do
39
+ specify { expect(reflection.keys).to eq([]) }
40
+ specify { expect(reflection(keys: ['a', :b]).keys).to eq(%w[a b]) }
41
+ specify { expect(reflection(keys: :c).keys).to eq(%w[c]) }
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Localized do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options)
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field)
13
+
14
+ expect(Target).to be_method_defined(:field_translations)
15
+ expect(Target).to be_method_defined(:field_translations=)
16
+ expect(Target).to be_method_defined(:field)
17
+ expect(Target).to be_method_defined(:field=)
18
+ expect(Target).to be_method_defined(:field?)
19
+ expect(Target).to be_method_defined(:field_before_type_cast)
20
+ end
21
+ end
22
+
23
+ describe '#generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias_translations)
30
+ expect(Target).to be_method_defined(:field_alias_translations=)
31
+ expect(Target).to be_method_defined(:field_alias)
32
+ expect(Target).to be_method_defined(:field_alias=)
33
+ expect(Target).to be_method_defined(:field_alias?)
34
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Reflections::Represents do
4
+ def reflection(options = {})
5
+ described_class.new(:field, options.reverse_merge(of: :subject))
6
+ end
7
+
8
+ describe '.build' do
9
+ before { stub_class(:target) }
10
+
11
+ specify do
12
+ described_class.build(Class.new, Target, :field, of: :subject)
13
+
14
+ expect(Target).to be_method_defined(:field)
15
+ expect(Target).to be_method_defined(:field=)
16
+ expect(Target).to be_method_defined(:field?)
17
+ expect(Target).to be_method_defined(:field_before_type_cast)
18
+ expect(Target).to be_method_defined(:field_default)
19
+ expect(Target).to be_method_defined(:field_values)
20
+ end
21
+ end
22
+
23
+ describe '.generate_methods' do
24
+ before { stub_class(:target) }
25
+
26
+ specify do
27
+ described_class.generate_methods(:field_alias, Target)
28
+
29
+ expect(Target).to be_method_defined(:field_alias)
30
+ expect(Target).to be_method_defined(:field_alias=)
31
+ expect(Target).to be_method_defined(:field_alias?)
32
+ expect(Target).to be_method_defined(:field_alias_before_type_cast)
33
+ expect(Target).to be_method_defined(:field_alias_default)
34
+ expect(Target).to be_method_defined(:field_alias_values)
35
+ end
36
+ end
37
+
38
+ describe '#type' do
39
+ specify { expect(reflection.type).to eq(Object) }
40
+ specify { expect(reflection(type: :whatever).type).to eq(Object) }
41
+ end
42
+
43
+ describe '#reference' do
44
+ specify { expect { reflection(of: nil) }.to raise_error ArgumentError }
45
+ specify { expect(reflection(of: :subject).reference).to eq('subject') }
46
+ end
47
+
48
+ describe '#column' do
49
+ specify { expect(reflection.column).to eq('field') }
50
+ specify { expect(reflection(column: 'hello').column).to eq('hello') }
51
+ end
52
+
53
+ describe '#reader' do
54
+ specify { expect(reflection.reader).to eq('field') }
55
+ specify { expect(reflection(column: 'hello').reader).to eq('hello') }
56
+ specify { expect(reflection(reader: 'world').reader).to eq('world') }
57
+ end
58
+
59
+ describe '#reader_before_type_cast' do
60
+ specify { expect(reflection.reader_before_type_cast).to eq('field_before_type_cast') }
61
+ specify { expect(reflection(column: 'hello').reader_before_type_cast).to eq('hello_before_type_cast') }
62
+ specify { expect(reflection(reader: 'world').reader_before_type_cast).to eq('world_before_type_cast') }
63
+ end
64
+
65
+ describe '#writer' do
66
+ specify { expect(reflection.writer).to eq('field=') }
67
+ specify { expect(reflection(column: 'hello').writer).to eq('hello=') }
68
+ specify { expect(reflection(writer: 'world').writer).to eq('world=') }
69
+ end
70
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes::Represents do
4
+ before { stub_model(:dummy) }
5
+
6
+ def attribute(*args)
7
+ options = args.extract_options!
8
+ Dummy.add_attribute(Granite::Form::Model::Attributes::Reflections::Represents, :full_name, options.reverse_merge(of: :subject))
9
+ Dummy.new.attribute(:full_name)
10
+ end
11
+
12
+ before do
13
+ stub_model :subject do
14
+ attribute :full_name, String
15
+ end
16
+ end
17
+
18
+ describe '#new' do
19
+ before { attribute(:full_name) }
20
+ let(:attributes) { {foo: 'bar'} }
21
+
22
+ specify { expect { Dummy.new(attributes) }.to_not change { attributes } }
23
+ end
24
+
25
+ describe '#write' do
26
+ subject { Subject.new }
27
+ before { allow_any_instance_of(Dummy).to receive_messages(value: 42, subject: subject) }
28
+ let(:field) { attribute }
29
+
30
+ specify { expect { field.write('hello') }.to change { subject.full_name }.to('hello') }
31
+ end
32
+
33
+ describe '#read' do
34
+ subject { Subject.new(full_name: :hello) }
35
+ before { allow_any_instance_of(Dummy).to receive_messages(value: 42, subject: subject) }
36
+ let(:field) { attribute(normalizer: ->(v) { v && v.is_a?(String) ? v.strip : v }, default: :world, enum: ['hello', '42', 'world', :world]) }
37
+
38
+ specify { expect(field.read).to eq('hello') }
39
+ specify { expect(field.tap { |r| r.write(nil) }.read).to eq(:world) }
40
+ specify { expect(field.tap { |r| r.write(:world) }.read).to eq('world') }
41
+ specify { expect(field.tap { |r| r.write('hello') }.read).to eq('hello') }
42
+ specify { expect(field.tap { |r| r.write(' hello ') }.read).to eq(nil) }
43
+ specify { expect(field.tap { |r| r.write(42) }.read).to eq('42') }
44
+ specify { expect(field.tap { |r| r.write(43) }.read).to eq(nil) }
45
+ specify { expect(field.tap { |r| r.write('') }.read).to eq(nil) }
46
+
47
+ specify { expect { subject.full_name = 42 }.to change { field.read }.to('42') }
48
+
49
+ context ':readonly' do
50
+ specify { expect(attribute(readonly: true).tap { |r| r.write('string') }.read).to eq('hello') }
51
+ end
52
+
53
+ context do
54
+ subject { Subject.new }
55
+ let(:field) { attribute(default: -> { Time.now.to_f }) }
56
+ specify { expect { sleep(0.01) }.not_to change { field.read } }
57
+ end
58
+ end
59
+
60
+ describe '#read_before_type_cast' do
61
+ subject { Subject.new(full_name: :hello) }
62
+ before { allow_any_instance_of(Dummy).to receive_messages(value: 42, subject: subject) }
63
+ let(:field) { attribute(normalizer: ->(v) { v.strip }, default: :world, enum: %w[hello 42 world]) }
64
+
65
+ specify { expect(field.read_before_type_cast).to eq(:hello) }
66
+ specify { expect(field.tap { |r| r.write(nil) }.read_before_type_cast).to eq(:world) }
67
+ specify { expect(field.tap { |r| r.write(:world) }.read_before_type_cast).to eq(:world) }
68
+ specify { expect(field.tap { |r| r.write('hello') }.read_before_type_cast).to eq('hello') }
69
+ specify { expect(field.tap { |r| r.write(42) }.read_before_type_cast).to eq(42) }
70
+ specify { expect(field.tap { |r| r.write(43) }.read_before_type_cast).to eq(43) }
71
+ specify { expect(field.tap { |r| r.write('') }.read_before_type_cast).to eq('') }
72
+
73
+ specify { expect { subject.full_name = 42 }.to change { field.read_before_type_cast }.to(42) }
74
+
75
+ context ':readonly' do
76
+ specify { expect(attribute(readonly: true).tap { |r| r.write('string') }.read_before_type_cast).to eq(:hello) }
77
+ end
78
+
79
+ context do
80
+ subject { Subject.new }
81
+ let(:field) { attribute(default: -> { Time.now.to_f }) }
82
+ specify { expect { sleep(0.01) }.not_to change { field.read_before_type_cast } }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,350 @@
1
+ require 'spec_helper'
2
+
3
+ describe Granite::Form::Model::Attributes do
4
+ let(:model) do
5
+ stub_model do
6
+ include Granite::Form::Model::Associations
7
+ include Granite::Form::Model::Localization
8
+
9
+ attribute :id, Integer
10
+ attribute :full_name, String
11
+ alias_attribute :name, :full_name
12
+
13
+ localized :t, String
14
+ alias_attribute :title, :t
15
+
16
+ embeds_one(:author) {}
17
+ embeds_many(:projects) {}
18
+ end
19
+ end
20
+
21
+ describe '.reflect_on_attribute' do
22
+ specify { expect(model.reflect_on_attribute(:full_name).name).to eq('full_name') }
23
+ specify { expect(model.reflect_on_attribute('full_name').name).to eq('full_name') }
24
+ specify { expect(model.reflect_on_attribute(:name).name).to eq('full_name') }
25
+ specify { expect(model.reflect_on_attribute(:foobar)).to be_nil }
26
+ end
27
+
28
+ describe '.has_attribute?' do
29
+ specify { expect(model.has_attribute?(:full_name)).to eq(true) }
30
+ specify { expect(model.has_attribute?('full_name')).to eq(true) }
31
+ specify { expect(model.has_attribute?(:name)).to eq(true) }
32
+ specify { expect(model.has_attribute?(:foobar)).to eq(false) }
33
+ end
34
+
35
+ describe '.attribute_names' do
36
+ specify { expect(stub_model.attribute_names).to eq([]) }
37
+ specify { expect(model.attribute_names).to eq(%w[id full_name t author projects]) }
38
+ specify { expect(model.attribute_names(false)).to eq(%w[id full_name t]) }
39
+ end
40
+
41
+ describe '.inspect' do
42
+ specify { expect(stub_model.inspect).to match(/#<Class:0x\w+>\(no attributes\)/) }
43
+ specify { expect(stub_model(:user).inspect).to eq('User(no attributes)') }
44
+ specify do
45
+ expect(stub_model do
46
+ include Granite::Form::Model::Primary
47
+ primary :count, Integer
48
+ attribute :object, Object
49
+ end.inspect).to match(/#<Class:0x\w+>\(\*count: Integer, object: Object\)/) end
50
+ specify do
51
+ expect(stub_model(:user) do
52
+ include Granite::Form::Model::Primary
53
+ primary :count, Integer
54
+ attribute :object, Object
55
+ end.inspect).to match('User(*count: Integer, object: Object)') end
56
+ end
57
+
58
+ describe '#==' do
59
+ let(:model) do
60
+ stub_model do
61
+ attribute :name, String
62
+ attribute :count, Float, default: 0
63
+ end
64
+ end
65
+ subject { model.new name: 'hello', count: 42 }
66
+
67
+ it { is_expected.not_to eq(nil) }
68
+ it { is_expected.not_to eq('hello') }
69
+ it { is_expected.not_to eq(Object.new) }
70
+ it { is_expected.not_to eq(model.new) }
71
+ it { is_expected.not_to eq(model.new(name: 'hello1', count: 42)) }
72
+ it { is_expected.not_to eq(model.new(name: 'hello', count: 42.1)) }
73
+ it { is_expected.to eq(model.new(name: 'hello', count: 42)) }
74
+
75
+ it { is_expected.not_to eql(nil) }
76
+ it { is_expected.not_to eql('hello') }
77
+ it { is_expected.not_to eql(Object.new) }
78
+ it { is_expected.not_to eql(model.new) }
79
+ it { is_expected.not_to eql(model.new(name: 'hello1', count: 42)) }
80
+ it { is_expected.not_to eql(model.new(name: 'hello', count: 42.1)) }
81
+ it { is_expected.to eql(model.new(name: 'hello', count: 42)) }
82
+ end
83
+
84
+ describe '#attribute' do
85
+ let(:instance) { model.new }
86
+ specify { expect(instance.attribute(:full_name).reflection.name).to eq('full_name') }
87
+ specify { expect(instance.attribute('full_name').reflection.name).to eq('full_name') }
88
+ specify { expect(instance.attribute(:name).reflection.name).to eq('full_name') }
89
+ specify { expect(instance.attribute(:foobar)).to be_nil }
90
+
91
+ specify { expect(instance.attribute('full_name')).to equal(instance.attribute(:name)) }
92
+ end
93
+
94
+ describe '#has_attribute?' do
95
+ specify { expect(model.new.has_attribute?(:full_name)).to eq(true) }
96
+ specify { expect(model.new.has_attribute?('full_name')).to eq(true) }
97
+ specify { expect(model.new.has_attribute?(:name)).to eq(true) }
98
+ specify { expect(model.new.has_attribute?(:foobar)).to eq(false) }
99
+ end
100
+
101
+ describe '#attribute_names' do
102
+ specify { expect(stub_model.new.attribute_names).to eq([]) }
103
+ specify { expect(model.new.attribute_names).to eq(%w[id full_name t author projects]) }
104
+ specify { expect(model.new.attribute_names(false)).to eq(%w[id full_name t]) }
105
+ end
106
+
107
+ describe '#attribute_present?' do
108
+ specify { expect(model.new.attribute_present?(:name)).to be(false) }
109
+ specify { expect(model.new(name: '').attribute_present?(:name)).to be(false) }
110
+ specify { expect(model.new(name: 'Name').attribute_present?(:name)).to be(true) }
111
+ end
112
+
113
+ describe '#attributes' do
114
+ specify { expect(stub_model.new.attributes).to eq({}) }
115
+ specify do
116
+ expect(model.new(name: 'Name').attributes)
117
+ .to match('id' => nil, 'full_name' => 'Name', 't' => {}, 'author' => nil, 'projects' => nil)
118
+ end
119
+ specify do
120
+ expect(model.new(name: 'Name').attributes(false))
121
+ .to match('id' => nil, 'full_name' => 'Name', 't' => {})
122
+ end
123
+ end
124
+
125
+ describe '#assign_attributes' do
126
+ let(:attributes) { {id: 42, full_name: 'Name', missed: 'value'} }
127
+ subject { model.new }
128
+
129
+ specify { expect { subject.assign_attributes(attributes) }.to change { subject.id }.to(42) }
130
+ specify { expect { subject.assign_attributes(attributes) }.to change { subject.full_name }.to('Name') }
131
+
132
+ context 'features stack and assign order' do
133
+ let(:model) do
134
+ stub_model do
135
+ attr_reader :logger
136
+
137
+ def self.log(a)
138
+ define_method("#{a}=") do |*args|
139
+ log(a)
140
+ super(*args)
141
+ end
142
+ end
143
+
144
+ def log(o)
145
+ (@logger ||= []).push(o)
146
+ end
147
+
148
+ attribute :plain1, String
149
+ attribute :plain2, String
150
+ log(:plain1)
151
+ log(:plain2)
152
+ end
153
+ end
154
+ subject { model.new }
155
+
156
+ specify do
157
+ expect { subject.assign_attributes(plain1: 'value', plain2: 'value') }
158
+ .to change { subject.logger }.to(%i[plain1 plain2])
159
+ end
160
+
161
+ specify do
162
+ expect { subject.assign_attributes(plain2: 'value', plain1: 'value') }
163
+ .to change { subject.logger }.to(%i[plain2 plain1])
164
+ end
165
+
166
+ context do
167
+ before do
168
+ model.class_eval do
169
+ include Granite::Form::Model::Representation
170
+ include Granite::Form::Model::Associations
171
+
172
+ embeds_one :assoc do
173
+ attribute :assoc_plain, String
174
+ end
175
+ accepts_nested_attributes_for :assoc
176
+
177
+ represents :assoc_plain, of: :assoc
178
+
179
+ log(:assoc_attributes)
180
+ log(:assoc_plain)
181
+
182
+ def assign_attributes(attrs)
183
+ super attrs.merge(attrs.extract!('plain2'))
184
+ end
185
+ end
186
+ end
187
+
188
+ specify do
189
+ expect { subject.assign_attributes(assoc_plain: 'value', assoc_attributes: {}, plain1: 'value', plain2: 'value') }
190
+ .to change { subject.logger }.to(%i[plain1 assoc_attributes assoc_plain plain2])
191
+ end
192
+
193
+ specify do
194
+ expect { subject.assign_attributes(plain1: 'value', plain2: 'value', assoc_plain: 'value', assoc_attributes: {}) }
195
+ .to change { subject.logger }.to(%i[plain1 assoc_attributes assoc_plain plain2])
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#inspect' do
202
+ specify { expect(stub_model.new.inspect).to match(/#<#<Class:0x\w+> \(no attributes\)>/) }
203
+ specify { expect(stub_model(:user).new.inspect).to match(/#<User \(no attributes\)>/) }
204
+ specify do
205
+ expect(stub_model do
206
+ include Granite::Form::Model::Primary
207
+ primary :count, Integer
208
+ attribute :object, Object
209
+ end.new(object: 'String').inspect).to match(/#<#<Class:0x\w+> \*count: nil, object: "String">/) end
210
+ specify do
211
+ expect(stub_model(:user) do
212
+ include Granite::Form::Model::Primary
213
+ primary :count, Integer
214
+ attribute :object, Object
215
+ end.new.inspect).to match(/#<User \*count: nil, object: nil>/) end
216
+ end
217
+
218
+ context 'attributes integration' do
219
+ let(:model) do
220
+ stub_class do
221
+ include Granite::Form::Model::Attributes
222
+ include Granite::Form::Model::Associations
223
+ attr_accessor :name
224
+
225
+ attribute :id, Integer
226
+ attribute :hello, Object
227
+ attribute :string, String, default: ->(record) { record.name }
228
+ attribute :count, Integer, default: '10'
229
+ attribute(:calc, Integer) { 2 + 3 }
230
+ attribute :enum, Integer, enum: [1, 2, 3]
231
+ attribute :enum_with_default, Integer, enum: [1, 2, 3], default: '2'
232
+ attribute :foo, Boolean, default: false
233
+ collection :array, Integer, enum: [1, 2, 3], default: 7
234
+
235
+ def initialize(name = nil)
236
+ super()
237
+ @name = name
238
+ end
239
+ end
240
+ end
241
+
242
+ subject { model.new('world') }
243
+
244
+ its(:enum_values) { should == [1, 2, 3] }
245
+ its(:string_default) { should == 'world' }
246
+ its(:count_default) { should == '10' }
247
+ its(:name) { should == 'world' }
248
+ its(:hello) { should eq(nil) }
249
+ its(:hello?) { should eq(false) }
250
+ its(:count) { should == 10 }
251
+ its(:count_before_type_cast) { should == '10' }
252
+ its(:count_came_from_user?) { should eq(false) }
253
+ its(:count?) { should eq(true) }
254
+ its(:calc) { should == 5 }
255
+ its(:enum?) { should eq(false) }
256
+ its(:enum_with_default?) { should eq(true) }
257
+ specify { expect { subject.hello = 'worlds' }.to change { subject.hello }.from(nil).to('worlds') }
258
+ specify { expect { subject.count = 20 }.to change { subject.count }.from(10).to(20) }
259
+ specify { expect { subject.calc = 15 }.to change { subject.calc }.from(5).to(15) }
260
+ specify { expect { subject.count = '11' }.to change { subject.count_came_from_user? }.from(false).to(true) }
261
+
262
+ context 'enums' do
263
+ specify do
264
+ subject.enum = 3
265
+ expect(subject.enum).to eq(3)
266
+ end
267
+ specify do
268
+ subject.enum = '3'
269
+ expect(subject.enum).to eq(3)
270
+ end
271
+ specify do
272
+ subject.enum = 10
273
+ expect(subject.enum).to eq(nil)
274
+ end
275
+ specify do
276
+ subject.enum = 'hello'
277
+ expect(subject.enum).to eq(nil)
278
+ end
279
+ specify do
280
+ subject.enum_with_default = 3
281
+ expect(subject.enum_with_default).to eq(3)
282
+ end
283
+ specify do
284
+ subject.enum_with_default = 10
285
+ expect(subject.enum_with_default).to be_nil
286
+ end
287
+ end
288
+
289
+ context 'array' do
290
+ specify do
291
+ subject.array = [2, 4]
292
+ expect(subject.array).to eq([2, nil])
293
+ end
294
+ specify do
295
+ subject.array = [2, 4]
296
+ expect(subject.array?).to eq(true)
297
+ end
298
+ specify do
299
+ subject.array = [2, 4]
300
+ expect(subject.array_values).to eq([1, 2, 3])
301
+ end
302
+ specify do
303
+ subject.array = [2, 4]
304
+ expect(subject.array_default).to eq(7)
305
+ end
306
+ end
307
+
308
+ context 'attribute caching' do
309
+ before do
310
+ subject.hello = 'blabla'
311
+ subject.hello
312
+ subject.hello = 'newnewnew'
313
+ end
314
+
315
+ specify { expect(subject.hello).to eq('newnewnew') }
316
+ end
317
+ end
318
+
319
+ context 'inheritance' do
320
+ let!(:ancestor) do
321
+ Class.new do
322
+ include Granite::Form::Model::Attributes
323
+ attribute :foo, String
324
+ end
325
+ end
326
+
327
+ let!(:descendant1) do
328
+ Class.new ancestor do
329
+ attribute :bar, String
330
+ end
331
+ end
332
+
333
+ let!(:descendant2) do
334
+ Class.new ancestor do
335
+ attribute :baz, String
336
+ attribute :moo, String
337
+ end
338
+ end
339
+
340
+ specify { expect(ancestor._attributes.keys).to eq(['foo']) }
341
+ specify { expect(ancestor.instance_methods).to include :foo, :foo= }
342
+ specify { expect(ancestor.instance_methods).not_to include :bar, :bar=, :baz, :baz= }
343
+ specify { expect(descendant1._attributes.keys).to eq(%w[foo bar]) }
344
+ specify { expect(descendant1.instance_methods).to include :foo, :foo=, :bar, :bar= }
345
+ specify { expect(descendant1.instance_methods).not_to include :baz, :baz= }
346
+ specify { expect(descendant2._attributes.keys).to eq(%w[foo baz moo]) }
347
+ specify { expect(descendant2.instance_methods).to include :foo, :foo=, :baz, :baz=, :moo, :moo= }
348
+ specify { expect(descendant2.instance_methods).not_to include :bar, :bar= }
349
+ end
350
+ end