typed_eav 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/MIT-LICENSE +21 -0
- data/README.md +494 -0
- data/Rakefile +13 -0
- data/app/models/typed_eav/application_record.rb +7 -0
- data/app/models/typed_eav/field/base.rb +234 -0
- data/app/models/typed_eav/field/boolean.rb +22 -0
- data/app/models/typed_eav/field/color.rb +16 -0
- data/app/models/typed_eav/field/date.rb +24 -0
- data/app/models/typed_eav/field/date_array.rb +34 -0
- data/app/models/typed_eav/field/date_time.rb +29 -0
- data/app/models/typed_eav/field/decimal.rb +30 -0
- data/app/models/typed_eav/field/decimal_array.rb +31 -0
- data/app/models/typed_eav/field/email.rb +40 -0
- data/app/models/typed_eav/field/integer.rb +30 -0
- data/app/models/typed_eav/field/integer_array.rb +68 -0
- data/app/models/typed_eav/field/json.rb +26 -0
- data/app/models/typed_eav/field/long_text.rb +19 -0
- data/app/models/typed_eav/field/multi_select.rb +41 -0
- data/app/models/typed_eav/field/select.rb +28 -0
- data/app/models/typed_eav/field/text.rb +41 -0
- data/app/models/typed_eav/field/text_array.rb +36 -0
- data/app/models/typed_eav/field/url.rb +40 -0
- data/app/models/typed_eav/option.rb +24 -0
- data/app/models/typed_eav/section.rb +25 -0
- data/app/models/typed_eav/value.rb +149 -0
- data/db/migrate/20260330000000_create_typed_eav_tables.rb +132 -0
- data/lib/generators/typed_eav/install/install_generator.rb +28 -0
- data/lib/generators/typed_eav/scaffold/scaffold_generator.rb +106 -0
- data/lib/generators/typed_eav/scaffold/templates/config/initializers/typed_eav.rb +45 -0
- data/lib/generators/typed_eav/scaffold/templates/controllers/concerns/typed_eav_controller_concern.rb +24 -0
- data/lib/generators/typed_eav/scaffold/templates/controllers/typed_eav_controller.rb +231 -0
- data/lib/generators/typed_eav/scaffold/templates/helpers/typed_eav_helper.rb +150 -0
- data/lib/generators/typed_eav/scaffold/templates/javascript/controllers/array_field_controller.js +64 -0
- data/lib/generators/typed_eav/scaffold/templates/javascript/controllers/typed_eav_form_controller.js +32 -0
- data/lib/generators/typed_eav/scaffold/templates/views/shared/_array_field.html.erb +23 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/edit.html.erb +47 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/finders/_form.html.erb +80 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_boolean.html.erb +12 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_color.html.erb +11 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_common_fields.html.erb +57 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_date.html.erb +21 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_date_array.html.erb +16 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_date_time.html.erb +21 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_decimal.html.erb +21 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_decimal_array.html.erb +16 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_email.html.erb +11 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_integer.html.erb +21 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_integer_array.html.erb +16 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_json.html.erb +11 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_long_text.html.erb +21 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_multi_select.html.erb +6 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_select.html.erb +14 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_text.html.erb +26 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_text_array.html.erb +16 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_url.html.erb +11 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/index.html.erb +42 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/new.html.erb +7 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/show.html.erb +44 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_boolean.html.erb +10 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_color.html.erb +4 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_date.html.erb +6 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_date_array.html.erb +9 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_date_time.html.erb +5 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_decimal.html.erb +6 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_decimal_array.html.erb +9 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_email.html.erb +5 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_integer.html.erb +6 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_integer_array.html.erb +9 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_json.html.erb +7 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_long_text.html.erb +7 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_multi_select.html.erb +7 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_select.html.erb +7 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_text.html.erb +6 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_text_array.html.erb +9 -0
- data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_url.html.erb +5 -0
- data/lib/typed_eav/column_mapping.rb +64 -0
- data/lib/typed_eav/config.rb +91 -0
- data/lib/typed_eav/engine.rb +20 -0
- data/lib/typed_eav/has_typed_eav.rb +484 -0
- data/lib/typed_eav/query_builder.rb +133 -0
- data/lib/typed_eav/registry.rb +52 -0
- data/lib/typed_eav/version.rb +5 -0
- data/lib/typed_eav.rb +86 -0
- metadata +146 -0
data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_common_fields.html.erb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<%# Shared common fields for all field definition forms.
|
|
2
|
+
Rendered inside a form_with block via:
|
|
3
|
+
render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<% if field.errors.any? %>
|
|
6
|
+
<div style="color: red; margin-bottom: 1rem;">
|
|
7
|
+
<h3><%= pluralize(field.errors.count, "error") %>:</h3>
|
|
8
|
+
<ul>
|
|
9
|
+
<% field.errors.each do |error| %>
|
|
10
|
+
<li><%= error.full_message %></li>
|
|
11
|
+
<% end %>
|
|
12
|
+
</ul>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<% if field.new_record? %>
|
|
17
|
+
<input type="hidden" name="type" value="<%= field.field_type_name %>" />
|
|
18
|
+
<% end %>
|
|
19
|
+
|
|
20
|
+
<div>
|
|
21
|
+
<label>Field Type</label>
|
|
22
|
+
<input type="text" value="<%= field.field_type_name.humanize %>" disabled readonly />
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div>
|
|
26
|
+
<%= f.label :entity_type %>
|
|
27
|
+
<% if field.persisted? %>
|
|
28
|
+
<%= f.text_field :entity_type, disabled: true %>
|
|
29
|
+
<% else %>
|
|
30
|
+
<%= f.select :entity_type, TypedEAV.registry.entity_types, { prompt: "Select entity..." } %>
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div>
|
|
35
|
+
<%= f.label :name %>
|
|
36
|
+
<%= f.text_field :name, required: true %>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div>
|
|
40
|
+
<label>Scope</label>
|
|
41
|
+
<%# The controller derives scope from the ambient value (TypedEAV.current_scope)
|
|
42
|
+
and never permits it from params. Display it read-only so users can see which
|
|
43
|
+
partition this field belongs to without implying they can change it. %>
|
|
44
|
+
<input type="text"
|
|
45
|
+
value="<%= field.scope.presence || (field.new_record? ? (TypedEAV.current_scope || "Global (all scopes)") : "Global (all scopes)") %>"
|
|
46
|
+
disabled readonly />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div>
|
|
50
|
+
<%= f.label :required %>
|
|
51
|
+
<%= f.check_box :required %>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div>
|
|
55
|
+
<%= f.label :sort_order %>
|
|
56
|
+
<%= f.number_field :sort_order, min: 0 %>
|
|
57
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_date, "Earliest Date" %>
|
|
7
|
+
<%= f.date_field :min_date, value: field.min_date %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_date, "Latest Date" %>
|
|
12
|
+
<%= f.date_field :max_date, value: field.max_date %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div>
|
|
16
|
+
<%= f.label :default_value %>
|
|
17
|
+
<%= f.date_field :default_value, value: field.default_value %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
21
|
+
<% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_size, "Min Items" %>
|
|
7
|
+
<%= f.number_field :min_size, min: 0, value: field.min_size %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_size, "Max Items" %>
|
|
12
|
+
<%= f.number_field :max_size, min: 1, value: field.max_size %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
16
|
+
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_datetime, "Earliest" %>
|
|
7
|
+
<%= f.datetime_local_field :min_datetime, value: field.min_datetime %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_datetime, "Latest" %>
|
|
12
|
+
<%= f.datetime_local_field :max_datetime, value: field.max_datetime %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div>
|
|
16
|
+
<%= f.label :default_value %>
|
|
17
|
+
<%= f.datetime_local_field :default_value, value: field.default_value %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
21
|
+
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min %>
|
|
7
|
+
<%= f.number_field :min, value: field.min, step: "any" %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max %>
|
|
12
|
+
<%= f.number_field :max, value: field.max, step: "any" %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div>
|
|
16
|
+
<%= f.label :default_value %>
|
|
17
|
+
<%= f.number_field :default_value, value: field.default_value, step: "any" %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
21
|
+
<% end %>
|
data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_decimal_array.html.erb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_size, "Min Items" %>
|
|
7
|
+
<%= f.number_field :min_size, min: 0, value: field.min_size %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_size, "Max Items" %>
|
|
12
|
+
<%= f.number_field :max_size, min: 1, value: field.max_size %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
16
|
+
<% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :default_value %>
|
|
7
|
+
<%= f.text_field :default_value, value: field.default_value %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min %>
|
|
7
|
+
<%= f.number_field :min, value: field.min %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max %>
|
|
12
|
+
<%= f.number_field :max, value: field.max %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div>
|
|
16
|
+
<%= f.label :default_value %>
|
|
17
|
+
<%= f.number_field :default_value, value: field.default_value %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
21
|
+
<% end %>
|
data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/forms/_integer_array.html.erb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_size, "Min Items" %>
|
|
7
|
+
<%= f.number_field :min_size, min: 0, value: field.min_size %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_size, "Max Items" %>
|
|
12
|
+
<%= f.number_field :max_size, min: 1, value: field.max_size %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
16
|
+
<% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :default_value %>
|
|
7
|
+
<%= f.text_field :default_value, value: field.default_value %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_length %>
|
|
7
|
+
<%= f.number_field :min_length, min: 0, value: field.min_length %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_length %>
|
|
12
|
+
<%= f.number_field :max_length, min: 1, value: field.max_length %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div>
|
|
16
|
+
<%= f.label :default_value %>
|
|
17
|
+
<%= f.text_area :default_value, value: field.default_value, rows: 3 %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
21
|
+
<% end %>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
6
|
+
<% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<% if field.persisted? && field.field_options.any? %>
|
|
6
|
+
<div>
|
|
7
|
+
<%= f.label :default_value %>
|
|
8
|
+
<%= f.select :default_value, field.field_options.sorted.map { |o| [o.label, o.value] },
|
|
9
|
+
{ include_blank: "None", selected: field.default_value } %>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_length %>
|
|
7
|
+
<%= f.number_field :min_length, min: 0, value: field.min_length %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_length %>
|
|
12
|
+
<%= f.number_field :max_length, min: 1, value: field.max_length %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div>
|
|
16
|
+
<%= f.label :pattern, "Regex Pattern" %>
|
|
17
|
+
<%= f.text_field :pattern, value: field.pattern, placeholder: "e.g. ^[A-Z]+" %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div>
|
|
21
|
+
<%= f.label :default_value %>
|
|
22
|
+
<%= f.text_field :default_value, value: field.default_value %>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
26
|
+
<% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :min_size, "Min Items" %>
|
|
7
|
+
<%= f.number_field :min_size, min: 0, value: field.min_size %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<%= f.label :max_size, "Max Items" %>
|
|
12
|
+
<%= f.number_field :max_size, min: 1, value: field.max_size %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
16
|
+
<% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= form_with(model: field, scope: :typed_eav_field,
|
|
2
|
+
url: field.persisted? ? typed_eav_field_path(field) : typed_eav_fields_path) do |f| %>
|
|
3
|
+
<%= render "typed_eav/forms/common_fields", f: f, field: field %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :default_value %>
|
|
7
|
+
<%= f.text_field :default_value, value: field.default_value %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div><%= f.submit field.persisted? ? "Update" : "Create" %></div>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<h1>Typed Fields</h1>
|
|
2
|
+
|
|
3
|
+
<div style="margin-bottom: 1rem;">
|
|
4
|
+
<%= form_with url: new_typed_eav_field_path, method: :get do |f| %>
|
|
5
|
+
<%= f.select :type, TypedEAV.config.type_names %>
|
|
6
|
+
<%= f.submit "New Field" %>
|
|
7
|
+
<% end %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<table>
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th></th>
|
|
14
|
+
<th>ID</th>
|
|
15
|
+
<th>Type</th>
|
|
16
|
+
<th>Entity</th>
|
|
17
|
+
<th>Name</th>
|
|
18
|
+
<th>Required</th>
|
|
19
|
+
<th>Scope</th>
|
|
20
|
+
<th>Options</th>
|
|
21
|
+
<th>Default</th>
|
|
22
|
+
<th></th>
|
|
23
|
+
</tr>
|
|
24
|
+
</thead>
|
|
25
|
+
<tbody>
|
|
26
|
+
<% @fields.each do |field| %>
|
|
27
|
+
<tr>
|
|
28
|
+
<td><%= link_to "Edit", edit_typed_eav_field_path(field) %></td>
|
|
29
|
+
<td><%= link_to "##{field.id}", typed_eav_field_path(field) %></td>
|
|
30
|
+
<td><%= field.field_type_name %></td>
|
|
31
|
+
<td><%= field.entity_type %></td>
|
|
32
|
+
<td><%= field.name %></td>
|
|
33
|
+
<td><%= field.required? ? "Yes" : "" %></td>
|
|
34
|
+
<td><code><%= field.scope.inspect %></code></td>
|
|
35
|
+
<td><code><%= field.options %></code></td>
|
|
36
|
+
<td><code><%= field.default_value.inspect %></code></td>
|
|
37
|
+
<td><%= button_to "Delete", typed_eav_field_path(field), method: :delete,
|
|
38
|
+
data: { turbo_confirm: "Delete this field and all its values?" } %></td>
|
|
39
|
+
</tr>
|
|
40
|
+
<% end %>
|
|
41
|
+
</tbody>
|
|
42
|
+
</table>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<h1><%= @field.field_type_name.humanize %> Field: <%= @field.name %></h1>
|
|
2
|
+
|
|
3
|
+
<dl>
|
|
4
|
+
<dt>ID</dt>
|
|
5
|
+
<dd><%= @field.id %></dd>
|
|
6
|
+
|
|
7
|
+
<dt>Type</dt>
|
|
8
|
+
<dd><%= @field.field_type_name %></dd>
|
|
9
|
+
|
|
10
|
+
<dt>Entity Type</dt>
|
|
11
|
+
<dd><%= @field.entity_type %></dd>
|
|
12
|
+
|
|
13
|
+
<dt>Name</dt>
|
|
14
|
+
<dd><%= @field.name %></dd>
|
|
15
|
+
|
|
16
|
+
<dt>Required</dt>
|
|
17
|
+
<dd><%= @field.required? ? "Yes" : "No" %></dd>
|
|
18
|
+
|
|
19
|
+
<dt>Scope</dt>
|
|
20
|
+
<dd><code><%= @field.scope.inspect %></code></dd>
|
|
21
|
+
|
|
22
|
+
<dt>Options</dt>
|
|
23
|
+
<dd><code><%= @field.options %></code></dd>
|
|
24
|
+
|
|
25
|
+
<dt>Default Value</dt>
|
|
26
|
+
<dd><code><%= @field.default_value.inspect %></code></dd>
|
|
27
|
+
|
|
28
|
+
<dt>Values Count</dt>
|
|
29
|
+
<dd><%= @field.values.count %></dd>
|
|
30
|
+
</dl>
|
|
31
|
+
|
|
32
|
+
<% if @field.optionable? && @field.field_options.any? %>
|
|
33
|
+
<h2>Options</h2>
|
|
34
|
+
<ul>
|
|
35
|
+
<% @field.field_options.sorted.each do |option| %>
|
|
36
|
+
<li><%= option.label %> (<code><%= option.value %></code>)</li>
|
|
37
|
+
<% end %>
|
|
38
|
+
</ul>
|
|
39
|
+
<% end %>
|
|
40
|
+
|
|
41
|
+
<div style="margin-top: 1rem;">
|
|
42
|
+
<%= link_to "Edit", edit_typed_eav_field_path(@field) %> |
|
|
43
|
+
<%= link_to "Back", typed_eav_fields_path %>
|
|
44
|
+
</div>
|
data/lib/generators/typed_eav/scaffold/templates/views/typed_eav/values/inputs/_boolean.html.erb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<%= form.label :value, field.name %>
|
|
3
|
+
<% if field.required? %>
|
|
4
|
+
<%= form.check_box :value, { checked: typed_value.value }, "true", "false" %>
|
|
5
|
+
<% else %>
|
|
6
|
+
<%= form.select :value,
|
|
7
|
+
[["", ""], ["Yes", "true"], ["No", "false"]],
|
|
8
|
+
{ selected: typed_value.value.nil? ? "" : typed_value.value.to_s } %>
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TypedEAV
|
|
4
|
+
# Maps field types to their native database column on the values table.
|
|
5
|
+
#
|
|
6
|
+
# This is the core concept borrowed from Relaticle's hybrid EAV:
|
|
7
|
+
# instead of serializing everything into a jsonb blob, each value
|
|
8
|
+
# type gets its own column so the database can natively index,
|
|
9
|
+
# sort, and enforce constraints.
|
|
10
|
+
#
|
|
11
|
+
# Usage in field type classes:
|
|
12
|
+
#
|
|
13
|
+
# class TypedEAV::Field::Integer < TypedEAV::Field::Base
|
|
14
|
+
# value_column :integer_value
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# The value model reads this to know which column to read/write.
|
|
18
|
+
# The query builder reads this to know which column to filter on.
|
|
19
|
+
# ActiveRecord handles all type casting automatically via the
|
|
20
|
+
# column's registered ActiveRecord::Type.
|
|
21
|
+
module ColumnMapping
|
|
22
|
+
extend ActiveSupport::Concern
|
|
23
|
+
|
|
24
|
+
DEFAULT_OPERATORS_BY_COLUMN = {
|
|
25
|
+
boolean_value: %i[eq not_eq is_null is_not_null],
|
|
26
|
+
string_value: %i[eq not_eq contains not_contains starts_with ends_with is_null is_not_null],
|
|
27
|
+
text_value: %i[eq not_eq contains not_contains starts_with ends_with is_null is_not_null],
|
|
28
|
+
integer_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null],
|
|
29
|
+
decimal_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null],
|
|
30
|
+
date_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null],
|
|
31
|
+
datetime_value: %i[eq not_eq gt gteq lt lteq between is_null is_not_null],
|
|
32
|
+
json_value: %i[contains is_null is_not_null],
|
|
33
|
+
}.freeze
|
|
34
|
+
FALLBACK_OPERATORS = %i[eq not_eq is_null is_not_null].freeze
|
|
35
|
+
|
|
36
|
+
class_methods do
|
|
37
|
+
# Declare which typed column this field type stores its value in.
|
|
38
|
+
def value_column(column_name = nil)
|
|
39
|
+
unless column_name
|
|
40
|
+
return @value_column || raise(NotImplementedError,
|
|
41
|
+
"#{name} must declare `value_column :column_name`")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@value_column = column_name.to_sym
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# All operators this field type supports for querying.
|
|
48
|
+
# Subclasses can override to restrict or extend.
|
|
49
|
+
def supported_operators
|
|
50
|
+
@supported_operators || default_operators_for(value_column)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def operators(*ops)
|
|
54
|
+
@supported_operators = ops.map(&:to_sym)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def default_operators_for(col)
|
|
60
|
+
DEFAULT_OPERATORS_BY_COLUMN.fetch(col, FALLBACK_OPERATORS)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|