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,26 @@
1
+ require 'granite/form/model/attributes/reflections/localized'
2
+ require 'granite/form/model/attributes/localized'
3
+
4
+ module Granite
5
+ module Form
6
+ module Model
7
+ module Localization
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ def localized(*args, &block)
12
+ add_attribute(Granite::Form::Model::Attributes::Reflections::Localized, *args, &block)
13
+ end
14
+
15
+ def fallbacks(locale)
16
+ ::I18n.respond_to?(:fallbacks) ? ::I18n.fallbacks[locale] : [locale]
17
+ end
18
+
19
+ def locale
20
+ I18n.locale
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Persistence
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def instantiate(data)
9
+ data = data.stringify_keys
10
+ instance = allocate
11
+
12
+ instance.instance_variable_set(:@initial_attributes, data.slice(*attribute_names))
13
+ instance.send(:mark_persisted!)
14
+
15
+ instance
16
+ end
17
+
18
+ def instantiate_collection(data)
19
+ collection = Array.wrap(data).map { |attrs| instantiate attrs }
20
+ collection = scope(collection, true) if respond_to?(:scope)
21
+ collection
22
+ end
23
+ end
24
+
25
+ def persisted?
26
+ !!@persisted
27
+ end
28
+
29
+ def destroyed?
30
+ !!@destroyed
31
+ end
32
+
33
+ def marked_for_destruction?
34
+ @marked_for_destruction
35
+ end
36
+
37
+ def mark_for_destruction
38
+ @marked_for_destruction = true
39
+ end
40
+
41
+ def _destroy
42
+ marked_for_destruction?
43
+ end
44
+
45
+ private
46
+
47
+ def mark_persisted!
48
+ @persisted = true
49
+ @destroyed = false
50
+ end
51
+
52
+ def mark_destroyed!
53
+ @persisted = false
54
+ @destroyed = true
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,59 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Primary
5
+ extend ActiveSupport::Concern
6
+ DEFAULT_PRIMARY_ATTRIBUTE_OPTIONS = lambda do
7
+ {
8
+ type: Granite::Form::UUID,
9
+ default: -> { Granite::Form::UUID.random_create }
10
+ }
11
+ end
12
+
13
+ included do
14
+ class_attribute :_primary_name, instance_writer: false
15
+ delegate :has_primary_attribute?, to: 'self.class'
16
+
17
+ prepend PrependMethods
18
+ alias_method :eql?, :==
19
+ end
20
+
21
+ module ClassMethods
22
+ def primary(*args)
23
+ options = args.extract_options!
24
+ self._primary_name = (args.first.presence || Granite::Form.primary_attribute).to_s
25
+ unless has_attribute?(_primary_name)
26
+ options[:type] = args.second if args.second
27
+ attribute _primary_name, options.presence || DEFAULT_PRIMARY_ATTRIBUTE_OPTIONS.call
28
+ end
29
+ alias_attribute :primary_attribute, _primary_name
30
+ end
31
+
32
+ alias_method :primary_attribute, :primary
33
+
34
+ def has_primary_attribute? # rubocop:disable Naming/PredicateName
35
+ has_attribute? _primary_name
36
+ end
37
+
38
+ def primary_name
39
+ _primary_name
40
+ end
41
+ end
42
+
43
+ module PrependMethods
44
+ def ==(other)
45
+ if other.instance_of?(self.class) && has_primary_attribute?
46
+ if primary_attribute
47
+ primary_attribute == other.primary_attribute
48
+ else
49
+ object_id == other.object_id
50
+ end
51
+ else
52
+ super(other)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ require 'active_model/version'
2
+ require 'granite/form/model/attributes/reflections/represents'
3
+ require 'granite/form/model/attributes/represents'
4
+
5
+ module Granite
6
+ module Form
7
+ module Model
8
+ module Representation
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ prepend PrependMethods
13
+ end
14
+
15
+ module PrependMethods
16
+ def assign_attributes(attrs)
17
+ if self.class.represented_attributes.present?
18
+ attrs = attrs.to_unsafe_hash if attrs.respond_to?(:to_unsafe_hash)
19
+ attrs = attrs.stringify_keys
20
+ represented_attrs = self.class.represented_names_and_aliases
21
+ .each_with_object({}) do |name, result|
22
+ result[name] = attrs.delete(name) if attrs.key?(name)
23
+ end
24
+
25
+ super(attrs.merge!(represented_attrs))
26
+ else
27
+ super(attrs)
28
+ end
29
+ end
30
+
31
+ alias_method :attributes=, :assign_attributes
32
+ end
33
+
34
+ module ClassMethods
35
+ def represents(*names, &block)
36
+ options = names.extract_options!
37
+ names.each do |name|
38
+ add_attribute(Granite::Form::Model::Attributes::Reflections::Represents, name, options, &block)
39
+ end
40
+ end
41
+
42
+ def represented_attributes
43
+ @represented_attributes ||= _attributes.values.select do |attribute|
44
+ attribute.is_a? Granite::Form::Model::Attributes::Reflections::Represents
45
+ end
46
+ end
47
+
48
+ def represented_names_and_aliases
49
+ @represented_names_and_aliases ||= represented_attributes.flat_map do |attribute|
50
+ [attribute.name, *inverted_attribute_aliases[attribute.name]]
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def run_validations! #:nodoc:
58
+ super
59
+ emerge_represented_attributes_errors!
60
+ errors.empty?
61
+ end
62
+
63
+ # Move represent attribute errors to the top level:
64
+ #
65
+ # {:'role.email' => ['Some error']}
66
+ #
67
+ # to:
68
+ #
69
+ # {email: ['Some error']}
70
+ #
71
+ def emerge_represented_attributes_errors!
72
+ self.class.represented_attributes.each do |attribute|
73
+ move_errors(:"#{attribute.reference}.#{attribute.column}", attribute.column)
74
+ end
75
+ end
76
+
77
+ if ActiveModel.version >= Gem::Version.new('6.1.0')
78
+ def move_errors(from, to)
79
+ errors.where(from).each do |error|
80
+ options = error.options
81
+ # If we generate message for built-in validation, we don't want to later escape it in our monkey-patch
82
+ options = options.merge(message: error.message.html_safe) unless options.key?(:message)
83
+
84
+ errors.add(to, error.type, **options)
85
+ end
86
+
87
+ errors.delete(from)
88
+ end
89
+ else
90
+ # up to 6.0.x
91
+ def move_errors(from, to)
92
+ return unless errors.messages.key?(from) && errors.messages[from].present?
93
+
94
+ errors[to].concat(errors.messages[from])
95
+ errors.delete(from)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,118 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Scopes
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :_scope_base
9
+ scopify
10
+ end
11
+
12
+ module ScopeProxy
13
+ extend ActiveSupport::Concern
14
+
15
+ def self.for(model)
16
+ klass = Class.new(model._scope_base) do
17
+ include Granite::Form::Model::Scopes::ScopeProxy
18
+ end
19
+ klass.define_singleton_method(:_scope_model) { model }
20
+ model.const_set('ScopeProxy', klass)
21
+ end
22
+
23
+ included do
24
+ def initialize(source = nil, trust = false)
25
+ source ||= self.class.superclass.new
26
+
27
+ unless trust && source.is_a?(self.class)
28
+ source.each do |entity|
29
+ raise AssociationTypeMismatch.new(self.class._scope_model, entity.class) unless entity.is_a?(self.class._scope_model)
30
+ end
31
+ end
32
+
33
+ super source
34
+ end
35
+ end
36
+
37
+ def respond_to_missing?(method, _)
38
+ super || self.class._scope_model.respond_to?(method)
39
+ end
40
+
41
+ # rubocop:disable Style/MethodMissing
42
+ # rubocop-0.52.1 doesn't understand that `#respond_to_missing?` is defined above
43
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
44
+ def method_missing(method, *args, **kwargs, &block)
45
+ with_scope do
46
+ model = self.class._scope_model
47
+ if model.respond_to?(method)
48
+ result = model.public_send(method, *args, **kwargs, &block)
49
+ result.is_a?(Granite::Form::Model::Scopes) ? result : model.scope_class.new(result)
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+ elsif Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0')
56
+ def method_missing(method, *args, **kwargs, &block)
57
+ with_scope do
58
+ model = self.class._scope_model
59
+ if model.respond_to?(method)
60
+ model.public_send(method, *args, **kwargs, &block)
61
+ else
62
+ super
63
+ end
64
+ end
65
+ end
66
+ else
67
+ # up to 2.6.x
68
+ def method_missing(method, *args, &block)
69
+ with_scope do
70
+ model = self.class._scope_model
71
+ if model.respond_to?(method)
72
+ model.public_send(method, *args, &block)
73
+ else
74
+ super
75
+ end
76
+ end
77
+ end
78
+ end
79
+ # rubocop:enable Style/MethodMissing
80
+
81
+ def with_scope
82
+ previous_scope = self.class._scope_model.current_scope
83
+ self.class._scope_model.current_scope = self
84
+ result = yield
85
+ self.class._scope_model.current_scope = previous_scope
86
+ result
87
+ end
88
+ end
89
+
90
+ module ClassMethods
91
+ def scopify(scope_base = Array)
92
+ self._scope_base = scope_base
93
+ end
94
+
95
+ def scope_class
96
+ @scope_class ||= Granite::Form::Model::Scopes::ScopeProxy.for(self)
97
+ end
98
+
99
+ def scope(*args)
100
+ if args.empty?
101
+ current_scope
102
+ else
103
+ scope_class.new(*args)
104
+ end
105
+ end
106
+
107
+ def current_scope=(value)
108
+ @current_scope = value
109
+ end
110
+
111
+ def current_scope
112
+ @current_scope ||= scope_class.new
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,22 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Validations
5
+ class AssociatedValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ invalid_records = Array.wrap(value).reject do |r|
8
+ r.respond_to?(:valid?) && r.valid?(record.validation_context)
9
+ end
10
+ record.errors.add(attribute, :invalid, **options.merge(value: value)) if invalid_records.present?
11
+ end
12
+ end
13
+
14
+ module HelperMethods
15
+ def validates_associated(*attr_names)
16
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ require 'active_model/version'
2
+
3
+ module Granite
4
+ module Form
5
+ module Model
6
+ module Validations
7
+ class NestedValidator < ActiveModel::EachValidator
8
+ def self.validate_nested(record, name, value)
9
+ if value.is_a?(Enumerable)
10
+ value.each.with_index do |object, i|
11
+ import_errors(object.errors, record.errors, "#{name}.#{i}") if yield object
12
+ end
13
+ elsif value
14
+ import_errors(value.errors, record.errors, name.to_s) if yield value
15
+ end
16
+ end
17
+
18
+ if ActiveModel.version >= Gem::Version.new('6.1.0')
19
+ def self.import_errors(from, to, prefix)
20
+ from.each do |error|
21
+ key = "#{prefix}.#{error.attribute}"
22
+ to.import(error, attribute: key) unless to.added?(key, error.type, error.options)
23
+ end
24
+ end
25
+ else
26
+ # up to 6.0.x
27
+ def self.import_errors(from, to, prefix)
28
+ from.each do |key, message|
29
+ key = "#{prefix}.#{key}"
30
+ to[key] << message
31
+ to[key].uniq!
32
+ end
33
+ end
34
+ end
35
+
36
+ def validate_each(record, attribute, value)
37
+ self.class.validate_nested(record, attribute, value) do |object|
38
+ object.invalid? && !(object.respond_to?(:marked_for_destruction?) && object.marked_for_destruction?)
39
+ end
40
+ end
41
+ end
42
+
43
+ module HelperMethods
44
+ def validates_nested(*attr_names)
45
+ validates_with NestedValidator, _merge_attributes(attr_names)
46
+ end
47
+
48
+ def validates_nested?(attr)
49
+ _validators[attr.to_sym]
50
+ .grep(Granite::Form::Model::Validations::NestedValidator).present?
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Validations
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::Validations
7
+
8
+ included do
9
+ extend HelperMethods
10
+ include HelperMethods
11
+
12
+ alias_method :validate, :valid?
13
+ end
14
+
15
+ def validate!(context = nil)
16
+ valid?(context) || raise_validation_error
17
+ end
18
+
19
+ protected
20
+
21
+ def raise_validation_error
22
+ raise Granite::Form::ValidationError, self
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ Dir[File.dirname(__FILE__) + '/validations/*.rb'].each { |file| require file }
@@ -0,0 +1,33 @@
1
+ require 'granite/form/model/conventions'
2
+ require 'granite/form/model/attributes'
3
+ require 'granite/form/model/validations'
4
+ require 'granite/form/model/scopes'
5
+ require 'granite/form/model/primary'
6
+ require 'granite/form/model/lifecycle'
7
+ require 'granite/form/model/persistence'
8
+ require 'granite/form/model/callbacks'
9
+ require 'granite/form/model/associations'
10
+ require 'granite/form/model/localization'
11
+ require 'granite/form/model/representation'
12
+ require 'granite/form/model/dirty'
13
+
14
+ module Granite
15
+ module Form
16
+ module Model
17
+ extend ActiveSupport::Concern
18
+
19
+ included do
20
+ extend ActiveModel::Naming
21
+ extend ActiveModel::Translation
22
+
23
+ include ActiveModel::Conversion
24
+ include ActiveModel::Serialization
25
+ include ActiveModel::Serializers::JSON
26
+
27
+ include Conventions
28
+ include Attributes
29
+ include Validations
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Granite
2
+ module Form
3
+ class Railtie < Rails::Railtie
4
+ initializer 'granite.logger', after: 'active_record.logger' do
5
+ ActiveSupport.on_load(:active_record) { Granite::Form.logger ||= ActiveRecord::Base.logger }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'singleton'
2
+
3
+ module Granite
4
+ module Form
5
+ class UndefinedClass
6
+ include Singleton
7
+ end
8
+
9
+ UNDEFINED = UndefinedClass.instance.freeze
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Granite
2
+ module Form
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,163 @@
1
+ require 'tzinfo'
2
+ require 'active_support'
3
+ require 'active_support/deprecation'
4
+ require 'active_support/core_ext'
5
+ require 'active_support/concern'
6
+ require 'singleton'
7
+
8
+ require 'active_model'
9
+
10
+ require 'granite/form/version'
11
+ require 'granite/form/errors'
12
+ require 'granite/form/extensions'
13
+ require 'granite/form/undefined_class'
14
+ require 'granite/form/config'
15
+ require 'granite/form/railtie' if defined? Rails
16
+ require 'granite/form/model'
17
+ require 'granite/form/model/associations/persistence_adapters/base'
18
+ require 'granite/form/model/associations/persistence_adapters/active_record'
19
+
20
+ module Granite
21
+ module Form
22
+ BOOLEAN_MAPPING = {
23
+ 1 => true,
24
+ 0 => false,
25
+ '1' => true,
26
+ '0' => false,
27
+ 't' => true,
28
+ 'f' => false,
29
+ 'T' => true,
30
+ 'F' => false,
31
+ true => true,
32
+ false => false,
33
+ 'true' => true,
34
+ 'false' => false,
35
+ 'TRUE' => true,
36
+ 'FALSE' => false,
37
+ 'y' => true,
38
+ 'n' => false,
39
+ 'yes' => true,
40
+ 'no' => false
41
+ }.freeze
42
+
43
+ def self.config
44
+ Granite::Form::Config.instance
45
+ end
46
+
47
+ singleton_class.delegate(*Granite::Form::Config.delegated, to: :config)
48
+
49
+ typecaster('Object') { |value, attribute| value if value.class < attribute.type }
50
+ typecaster('String') { |value, _| value.to_s }
51
+ typecaster('Array') do |value|
52
+ case value
53
+ when ::Array then
54
+ value
55
+ when ::String then
56
+ value.split(',').map(&:strip)
57
+ end
58
+ end
59
+ typecaster('Hash') do |value|
60
+ case value
61
+ when ::Hash then
62
+ value
63
+ end
64
+ end
65
+ ActiveSupport.on_load :action_controller do
66
+ Granite::Form.typecaster('Hash') do |value|
67
+ case value
68
+ when ActionController::Parameters
69
+ value.to_h if value.permitted?
70
+ when ::Hash then
71
+ value
72
+ end
73
+ end
74
+ end
75
+ typecaster('Date') do |value|
76
+ begin
77
+ value.to_date
78
+ rescue ArgumentError, NoMethodError
79
+ nil
80
+ end
81
+ end
82
+ typecaster('DateTime') do |value|
83
+ begin
84
+ value.to_datetime
85
+ rescue ArgumentError
86
+ nil
87
+ end
88
+ end
89
+ typecaster('Time') do |value|
90
+ begin
91
+ value.is_a?(String) && ::Time.zone ? ::Time.zone.parse(value) : value.to_time
92
+ rescue ArgumentError
93
+ nil
94
+ end
95
+ end
96
+ typecaster('ActiveSupport::TimeZone') do |value|
97
+ case value
98
+ when ActiveSupport::TimeZone
99
+ value
100
+ when ::TZInfo::Timezone
101
+ ActiveSupport::TimeZone[value.name]
102
+ when String, Numeric, ActiveSupport::Duration
103
+ value = begin
104
+ Float(value)
105
+ rescue ArgumentError, TypeError
106
+ value
107
+ end
108
+ ActiveSupport::TimeZone[value]
109
+ end
110
+ end
111
+ typecaster('BigDecimal') do |value|
112
+ next unless value
113
+ begin
114
+ BigDecimal(Float(value).to_s)
115
+ rescue ArgumentError, TypeError
116
+ nil
117
+ end
118
+ end
119
+ typecaster('Float') do |value|
120
+ begin
121
+ Float(value)
122
+ rescue ArgumentError, TypeError
123
+ nil
124
+ end
125
+ end
126
+ typecaster('Integer') do |value|
127
+ begin
128
+ Float(value).to_i
129
+ rescue ArgumentError, TypeError
130
+ nil
131
+ end
132
+ end
133
+ typecaster('Boolean') { |value| BOOLEAN_MAPPING[value] }
134
+ typecaster('Granite::Form::UUID') do |value|
135
+ case value
136
+ when UUIDTools::UUID
137
+ Granite::Form::UUID.parse_raw value.raw
138
+ when Granite::Form::UUID
139
+ value
140
+ when String
141
+ Granite::Form::UUID.parse_string value
142
+ when Integer
143
+ Granite::Form::UUID.parse_int value
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ require 'granite/form/base'
150
+
151
+ Granite::Form.base_class = Granite::Form::Base
152
+
153
+ ActiveSupport.on_load :active_record do
154
+ require 'granite/form/active_record/associations'
155
+ require 'granite/form/active_record/nested_attributes'
156
+
157
+ include Granite::Form::ActiveRecord::Associations
158
+ singleton_class.prepend Granite::Form::ActiveRecord::NestedAttributes
159
+
160
+ def self.granite_persistence_adapter
161
+ Granite::Form::Model::Associations::PersistenceAdapters::ActiveRecord
162
+ end
163
+ end