phlexi-field 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/phlexi/field/builder.rb +9 -7
- data/lib/phlexi/field/options/associations.rb +2 -2
- data/lib/phlexi/field/options/attachments.rb +2 -2
- data/lib/phlexi/field/options/inferred_types.rb +40 -36
- data/lib/phlexi/field/options/multiple.rb +66 -0
- data/lib/phlexi/field/options/validators.rb +48 -0
- data/lib/phlexi/field/structure/dom.rb +1 -1
- data/lib/phlexi/field/structure/field_collection.rb +2 -0
- data/lib/phlexi/field/structure/namespace.rb +5 -5
- data/lib/phlexi/field/structure/node.rb +1 -1
- data/lib/phlexi/field/theme.rb +9 -6
- data/lib/phlexi/field/version.rb +1 -1
- data/lib/phlexi/field.rb +10 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 908d11456437457ad31bb719555098d0f1b9dce04fc4703548f5e1147896c3f8
|
4
|
+
data.tar.gz: 23e4bb8265525dd4acc3e7ad529fa31121b68b91751e12482173d42184a12b96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9936ccc9e4fcb1e7e7a0255036be6106962758a5b23e854fdf7434c029fcac389e9527c9717eee81384ebc0cb6cea173c98a018e7f100288fc6e6c15aecb9ef3
|
7
|
+
data.tar.gz: a766d9646d8d0e36967caf5be49cc5546e7909ad174326407011db6ad6695e3dcc56b001f2cd1882e40479b3d81d6fd066a486456eb8af356bff760563fa9be9
|
data/lib/phlexi/field/builder.rb
CHANGED
@@ -13,13 +13,15 @@ module Phlexi
|
|
13
13
|
# @attr_accessor [Object] value The value of the field.
|
14
14
|
class Builder < Structure::Node
|
15
15
|
include Phlex::Helpers
|
16
|
-
include Options::
|
17
|
-
include Options::Attachments
|
18
|
-
include Options::Descriptions
|
19
|
-
include Options::Hints
|
16
|
+
include Options::Validators
|
20
17
|
include Options::InferredTypes
|
18
|
+
include Options::Multiple
|
21
19
|
include Options::Labels
|
22
20
|
include Options::Placeholders
|
21
|
+
include Options::Descriptions
|
22
|
+
include Options::Hints
|
23
|
+
include Options::Associations
|
24
|
+
include Options::Attachments
|
23
25
|
|
24
26
|
class DOM < Structure::DOM; end
|
25
27
|
|
@@ -52,12 +54,12 @@ module Phlexi
|
|
52
54
|
self.class::FieldCollection.new(field: self, collection:, &)
|
53
55
|
end
|
54
56
|
|
55
|
-
protected
|
56
|
-
|
57
57
|
def has_value?
|
58
|
-
value.present?
|
58
|
+
attachment_reflection.present? ? value.attached? : (value.present? || value == false)
|
59
59
|
end
|
60
60
|
|
61
|
+
protected
|
62
|
+
|
61
63
|
def determine_initial_value(value)
|
62
64
|
return value unless value == NIL_VALUE
|
63
65
|
|
@@ -4,12 +4,12 @@ module Phlexi
|
|
4
4
|
module Field
|
5
5
|
module Options
|
6
6
|
module Associations
|
7
|
-
protected
|
8
|
-
|
9
7
|
def association_reflection
|
10
8
|
@association_reflection ||= find_association_reflection
|
11
9
|
end
|
12
10
|
|
11
|
+
protected
|
12
|
+
|
13
13
|
def find_association_reflection
|
14
14
|
if object.class.respond_to?(:reflect_on_association)
|
15
15
|
object.class.reflect_on_association(key)
|
@@ -4,12 +4,12 @@ module Phlexi
|
|
4
4
|
module Field
|
5
5
|
module Options
|
6
6
|
module Attachments
|
7
|
-
protected
|
8
|
-
|
9
7
|
def attachment_reflection
|
10
8
|
@attachment_reflection ||= find_attachment_reflection
|
11
9
|
end
|
12
10
|
|
11
|
+
protected
|
12
|
+
|
13
13
|
def find_attachment_reflection
|
14
14
|
if object.class.respond_to?(:reflect_on_attachment)
|
15
15
|
object.class.reflect_on_attachment(key)
|
@@ -6,44 +6,38 @@ module Phlexi
|
|
6
6
|
module Field
|
7
7
|
module Options
|
8
8
|
module InferredTypes
|
9
|
+
def inferred_field_component
|
10
|
+
@inferred_component ||= infer_field_component
|
11
|
+
end
|
12
|
+
|
9
13
|
def inferred_field_type
|
10
14
|
@inferred_field_type ||= infer_field_type
|
11
15
|
end
|
12
16
|
|
13
|
-
def
|
14
|
-
@
|
17
|
+
def inferred_string_field_type
|
18
|
+
@inferred_string_field_type || infer_string_field_type
|
15
19
|
end
|
16
20
|
|
17
21
|
private
|
18
22
|
|
19
23
|
def infer_field_component
|
20
|
-
|
21
|
-
when :string, :text
|
22
|
-
infer_string_field_component(key)
|
23
|
-
when :integer, :float, :decimal
|
24
|
-
:number
|
25
|
-
when :date, :datetime, :time
|
26
|
-
:date
|
27
|
-
when :boolean
|
28
|
-
:boolean
|
29
|
-
when :json, :jsonb, :hstore
|
30
|
-
:code
|
31
|
-
else
|
32
|
-
if association_reflection
|
33
|
-
:association
|
34
|
-
elsif attachment_reflection
|
35
|
-
:attachment
|
36
|
-
else
|
37
|
-
:text
|
38
|
-
end
|
39
|
-
end
|
24
|
+
inferred_field_type
|
40
25
|
end
|
41
26
|
|
42
27
|
def infer_field_type
|
28
|
+
# Check attachments first since they are implemented as associations
|
29
|
+
return :attachment if attachment_reflection
|
30
|
+
|
31
|
+
return :association if association_reflection
|
32
|
+
|
33
|
+
if object.class.respond_to?(:defined_enums)
|
34
|
+
return :enum if object.class.defined_enums.key?(key.to_s)
|
35
|
+
end
|
36
|
+
|
43
37
|
if object.class.respond_to?(:columns_hash)
|
44
|
-
# ActiveRecord
|
38
|
+
# ActiveRecord
|
45
39
|
column = object.class.columns_hash[key.to_s]
|
46
|
-
return column.type if column
|
40
|
+
return column.type if column&.type
|
47
41
|
end
|
48
42
|
|
49
43
|
if object.class.respond_to?(:attribute_types)
|
@@ -58,6 +52,12 @@ module Phlexi
|
|
58
52
|
return infer_field_type_from_value(object.send(key))
|
59
53
|
end
|
60
54
|
|
55
|
+
# Check if object is a has that contains key
|
56
|
+
if object.respond_to?(:fetch)
|
57
|
+
# Fallback to inferring type from the value
|
58
|
+
return infer_field_type_from_value(object.fetch(key))
|
59
|
+
end
|
60
|
+
|
61
61
|
# Default to string if we can't determine the type
|
62
62
|
:string
|
63
63
|
end
|
@@ -83,18 +83,24 @@ module Phlexi
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
return :password if is_password_field?
|
86
|
+
def infer_string_field_type
|
87
|
+
infer_string_field_type_from_key || infer_string_field_type_from_validations
|
88
|
+
end
|
90
89
|
|
91
|
-
|
92
|
-
return
|
90
|
+
def infer_string_field_type_from_validations
|
91
|
+
return unless has_validators?
|
93
92
|
|
94
|
-
:
|
93
|
+
if attribute_validators.find { |v| v.kind == :numericality }
|
94
|
+
:number
|
95
|
+
elsif attribute_validators.find { |v| v.kind == :format && v.options[:with] == URI::MailTo::EMAIL_REGEXP }
|
96
|
+
:email
|
97
|
+
end
|
95
98
|
end
|
96
99
|
|
97
|
-
def
|
100
|
+
def infer_string_field_type_from_key
|
101
|
+
key = self.key.to_s.downcase
|
102
|
+
return :password if is_password_field?(key)
|
103
|
+
|
98
104
|
custom_mappings = {
|
99
105
|
/url$|^link|^site/ => :url,
|
100
106
|
/^email/ => :email,
|
@@ -103,7 +109,7 @@ module Phlexi
|
|
103
109
|
/^time/ => :time,
|
104
110
|
/^date/ => :date,
|
105
111
|
/^number|_count$|_amount$/ => :number,
|
106
|
-
/^color
|
112
|
+
/^color|_color$/ => :color
|
107
113
|
}
|
108
114
|
|
109
115
|
custom_mappings.each do |pattern, type|
|
@@ -113,9 +119,7 @@ module Phlexi
|
|
113
119
|
nil
|
114
120
|
end
|
115
121
|
|
116
|
-
def is_password_field?
|
117
|
-
key = self.key.to_s.downcase
|
118
|
-
|
122
|
+
def is_password_field?(key)
|
119
123
|
exact_matches = ["password"]
|
120
124
|
prefixes = ["encrypted_"]
|
121
125
|
suffixes = ["_password", "_digest", "_hash", "_token"]
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Field
|
5
|
+
module Options
|
6
|
+
module Multiple
|
7
|
+
def multiple?
|
8
|
+
options[:multiple] = options.fetch(:multiple) { calculate_multiple_field_value }
|
9
|
+
end
|
10
|
+
|
11
|
+
def multiple!(multiple = true)
|
12
|
+
options[:multiple] = multiple
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def calculate_multiple_field_value
|
19
|
+
return true if attachment_reflection&.macro == :has_many_attached
|
20
|
+
return true if association_reflection&.macro == :has_many
|
21
|
+
return true if multiple_field_array_attribute?
|
22
|
+
|
23
|
+
check_multiple_field_from_validators
|
24
|
+
end
|
25
|
+
|
26
|
+
def multiple_field_array_attribute?
|
27
|
+
return false unless object.class.respond_to?(:columns_hash)
|
28
|
+
|
29
|
+
column = object.class.columns_hash[key.to_s]
|
30
|
+
return false unless column
|
31
|
+
|
32
|
+
case object.class.connection.adapter_name.downcase
|
33
|
+
when "postgresql"
|
34
|
+
column.array? || (column.type == :string && column.sql_type.include?("[]"))
|
35
|
+
end # || object.class.attribute_types[key.to_s].is_a?(ActiveRecord::Type::Serialized)
|
36
|
+
rescue
|
37
|
+
# Rails.logger.warn("Error checking multiple field array attribute: #{e.message}")
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_multiple_field_from_validators
|
42
|
+
inclusion_validator = find_validator(:inclusion)
|
43
|
+
length_validator = find_validator(:length)
|
44
|
+
|
45
|
+
return false unless inclusion_validator || length_validator
|
46
|
+
|
47
|
+
check_multiple_field_inclusion_validator(inclusion_validator) ||
|
48
|
+
check_multiple_field_length_validator(length_validator)
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_multiple_field_inclusion_validator(validator)
|
52
|
+
return false unless validator
|
53
|
+
in_option = validator.options[:in] || validator.options[:within]
|
54
|
+
return false unless in_option.is_a?(Array)
|
55
|
+
|
56
|
+
validator.options[:multiple] == true || (multiple_field_array_attribute? && in_option.size > 1)
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_multiple_field_length_validator(validator)
|
60
|
+
return false unless validator
|
61
|
+
validator.options[:maximum].to_i > 1 if validator.options[:maximum]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Field
|
5
|
+
module Options
|
6
|
+
module Validators
|
7
|
+
private
|
8
|
+
|
9
|
+
def has_validators?
|
10
|
+
@has_validators ||= object.class.respond_to?(:validators_on)
|
11
|
+
end
|
12
|
+
|
13
|
+
def attribute_validators
|
14
|
+
object.class.validators_on(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def association_reflection_validators
|
18
|
+
association_reflection ? object.class.validators_on(association_reflection.name) : []
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_validator?(validator)
|
22
|
+
!conditional_validators?(validator) && action_validator_match?(validator)
|
23
|
+
end
|
24
|
+
|
25
|
+
def conditional_validators?(validator)
|
26
|
+
validator.options.include?(:if) || validator.options.include?(:unless)
|
27
|
+
end
|
28
|
+
|
29
|
+
def action_validator_match?(validator)
|
30
|
+
return true unless validator.options.include?(:on)
|
31
|
+
|
32
|
+
case validator.options[:on]
|
33
|
+
when :save
|
34
|
+
true
|
35
|
+
when :create
|
36
|
+
!object.persisted?
|
37
|
+
when :update
|
38
|
+
object.persisted?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_validator(kind)
|
43
|
+
attribute_validators.find { |v| v.kind == kind && valid_validator?(v) } if has_validators?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -18,6 +18,8 @@ module Phlexi
|
|
18
18
|
class Namespace < Structure::Node
|
19
19
|
include Enumerable
|
20
20
|
|
21
|
+
class NamespaceCollection < Structure::NamespaceCollection; end
|
22
|
+
|
21
23
|
attr_reader :builder_klass, :object
|
22
24
|
|
23
25
|
def initialize(key, parent:, builder_klass:, object: nil)
|
@@ -71,7 +73,7 @@ module Phlexi
|
|
71
73
|
# to another `Namespace` or `Field`.
|
72
74
|
def nest_many(key, collection: nil, &)
|
73
75
|
collection ||= Array(object_value_for(key: key))
|
74
|
-
create_child(key, NamespaceCollection, collection:, &)
|
76
|
+
create_child(key, self.class::NamespaceCollection, collection:, &)
|
75
77
|
end
|
76
78
|
|
77
79
|
# Iterates through the children of the current namespace, which could be `Namespace` or `Field`
|
@@ -84,10 +86,8 @@ module Phlexi
|
|
84
86
|
@dom_id ||= begin
|
85
87
|
id = if object.nil?
|
86
88
|
nil
|
87
|
-
elsif
|
88
|
-
|
89
|
-
elsif object.respond_to?(:id)
|
90
|
-
object.id || :new
|
89
|
+
elsif (primary_key = Phlexi::Field.object_primary_key(object))
|
90
|
+
primary_key&.to_s || :new
|
91
91
|
end
|
92
92
|
[key, id].compact.join("_").underscore
|
93
93
|
end
|
data/lib/phlexi/field/theme.rb
CHANGED
@@ -2,11 +2,10 @@ require "fiber/local"
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Field
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
extend Fiber::Local
|
5
|
+
class Theme
|
6
|
+
def self.inherited(subclass)
|
7
|
+
super
|
8
|
+
subclass.extend Fiber::Local
|
10
9
|
end
|
11
10
|
|
12
11
|
# Retrieves the theme hash
|
@@ -26,8 +25,12 @@ module Phlexi
|
|
26
25
|
#
|
27
26
|
# @example Theme inheritance
|
28
27
|
# theme[:email] # Returns :text, indicating email inherits text's theme
|
28
|
+
def self.theme
|
29
|
+
raise NotImplementedError, "#{self} must implement #self.theme"
|
30
|
+
end
|
31
|
+
|
29
32
|
def theme
|
30
|
-
|
33
|
+
@theme ||= self.class.theme.freeze
|
31
34
|
end
|
32
35
|
|
33
36
|
# Recursively resolves the theme for a given property, handling nested symbol references
|
data/lib/phlexi/field/version.rb
CHANGED
data/lib/phlexi/field.rb
CHANGED
@@ -5,6 +5,8 @@ require "phlex"
|
|
5
5
|
require "active_support/core_ext/object/blank"
|
6
6
|
|
7
7
|
module Phlexi
|
8
|
+
NIL_VALUE = :__i_phlexi_i__
|
9
|
+
|
8
10
|
module Field
|
9
11
|
Loader = Zeitwerk::Loader.new.tap do |loader|
|
10
12
|
loader.tag = File.basename(__FILE__, ".rb")
|
@@ -19,8 +21,14 @@ module Phlexi
|
|
19
21
|
|
20
22
|
COMPONENT_BASE = (defined?(::ApplicationComponent) ? ::ApplicationComponent : Phlex::HTML)
|
21
23
|
|
22
|
-
NIL_VALUE = :__i_phlexi_i__
|
23
|
-
|
24
24
|
class Error < StandardError; end
|
25
|
+
|
26
|
+
def self.object_primary_key(object)
|
27
|
+
if object.class.respond_to?(:primary_key)
|
28
|
+
object.send(object.class.primary_key.to_sym)
|
29
|
+
elsif object.respond_to?(:id)
|
30
|
+
object.id
|
31
|
+
end
|
32
|
+
end
|
25
33
|
end
|
26
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phlexi-field
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|
@@ -208,7 +208,9 @@ files:
|
|
208
208
|
- lib/phlexi/field/options/hints.rb
|
209
209
|
- lib/phlexi/field/options/inferred_types.rb
|
210
210
|
- lib/phlexi/field/options/labels.rb
|
211
|
+
- lib/phlexi/field/options/multiple.rb
|
211
212
|
- lib/phlexi/field/options/placeholders.rb
|
213
|
+
- lib/phlexi/field/options/validators.rb
|
212
214
|
- lib/phlexi/field/structure/dom.rb
|
213
215
|
- lib/phlexi/field/structure/field_collection.rb
|
214
216
|
- lib/phlexi/field/structure/namespace.rb
|