terrazzo 0.7.3 → 0.7.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c052bdf0156ea6f274c81bd8e8ac10d2a0519149cf7cdc3f4a2e6ba7fad07457
4
- data.tar.gz: 52cc71522126bba003d340eb190dafda5c4f74e33c2be6605f3cecc1cadb793b
3
+ metadata.gz: '084ca2853c7f77397ca86d6530e21abe0d61819a464a6ce352b2c9230ae2d95f'
4
+ data.tar.gz: b2b7a278bd069090a23ef1c4edbb73a7e793af55eca3941225dff5695d90e92c
5
5
  SHA512:
6
- metadata.gz: b891e22f66ddccc7792b9096c250c727057161291d20eee0d0854bf5545bad6317512b32d9dd99a33619d93641178b53006682128f48ec7bb9d825440f17d6ef
7
- data.tar.gz: 7bf82c9b3eae347aacc43cb6dc0e521904363069dd802ec51a904f3dc9c53bebc316dc61609dd2565c877e0b0985a78f89fa1158325415db9dde9c8c9fd91d61
6
+ metadata.gz: e1dc0b5cb17ff57bf6261cd3eafc85583473d52e5898be93c54114972cc4d7d926a653bdbf1073ba8bec5b90c4526b314ec05d8c40e7771af608e2a986b920d8
7
+ data.tar.gz: 01b2c4e3ba0b24076b7a1c0bd90e8f55a70b0b03a11b04963fef2d130939350d0b34d0d95602c04ef9020f3029ac8afdab3220859c8b5f243cf5bb512ad3b5c7
data/README.md CHANGED
@@ -63,6 +63,9 @@ Full docs at **[gohypelab.github.io/terrazzo](https://gohypelab.github.io/terraz
63
63
  - Node.js 18+
64
64
  - A JS bundler (Vite recommended, esbuild also supported)
65
65
 
66
+ Terrazzo selects JSON 2.x because current Rails releases do not support the
67
+ JSON 3 argument format. See [the Rails compatibility fix](https://github.com/rails/rails/pull/58601).
68
+
66
69
  ## Development and releases
67
70
 
68
71
  Pull requests and pushes to `main` run the Ruby unit specs, npm package checks,
@@ -85,6 +88,14 @@ retried safely.
85
88
  See [RELEASING.md](RELEASING.md) for trusted publisher setup, release commands,
86
89
  and retry instructions.
87
90
 
91
+ ## Customizing search
92
+
93
+ Enable search with `searchable: true` in a dashboard field's options. Search supports
94
+ numeric columns, JSON store accessors, and associated records. Override the
95
+ controller's `search_resources` method to add custom search syntax.
96
+ See [search fields](docs/customizing-dashboards.md#search-fields) and
97
+ [custom search](docs/customizing-controller-actions.md#customizing-search).
98
+
88
99
  ## Customizing Per-Row Actions
89
100
 
90
101
  Terrazzo generates Show, Edit, and Destroy action buttons for each row on index pages and has_many tables on show pages.
@@ -158,9 +158,12 @@ module Terrazzo
158
158
  resource_class.all
159
159
  end
160
160
 
161
+ def search_resources
162
+ Terrazzo::Search.new(scoped_resource, dashboard, params[:search]).run
163
+ end
164
+
161
165
  def index_resources_and_order
162
- search = Terrazzo::Search.new(scoped_resource, dashboard, params[:search])
163
- resources = search.run
166
+ resources = search_resources
164
167
 
165
168
  filter = Terrazzo::Filter.new(resources, dashboard, params[:filter], params[:filter_value])
166
169
  resources = filter.run
@@ -1,6 +1,6 @@
1
1
  param_key = resource_class.model_name.param_key
2
2
 
3
- json.pageTitle t("terrazzo.actions.edit", resource_name: resource_name)
3
+ json.pageTitle "#{t("terrazzo.actions.edit", resource_name: resource_name)} — #{dashboard.display_resource(@resource)}"
4
4
 
5
5
  json.form do
6
6
  json.props({
@@ -9,11 +9,7 @@ module Terrazzo
9
9
  end
10
10
 
11
11
  def run
12
- if term.blank?
13
- scoped_resource
14
- else
15
- search_results
16
- end
12
+ term.blank? ? scoped_resource : search_results
17
13
  end
18
14
 
19
15
  private
@@ -21,52 +17,72 @@ module Terrazzo
21
17
  LIKE_ESCAPE = "\\"
22
18
 
23
19
  def search_results
24
- @association_search_joined = false
25
20
  searchable_attributes = dashboard.search_attributes
26
21
  return scoped_resource if searchable_attributes.empty?
27
22
 
28
- conditions = searchable_attributes.map do |attr|
29
- type = dashboard.attribute_type_for(attr)
23
+ conditions =
24
+ searchable_attributes
25
+ .map do |attr|
26
+ type = dashboard.attribute_type_for(attr)
30
27
 
31
- if type.respond_to?(:associative?) && type.associative?
32
- build_association_search(attr, type)
33
- else
34
- table = scoped_resource.model.arel_table
35
- table[attr].matches(search_pattern, LIKE_ESCAPE)
36
- end
37
- end.compact
28
+ if type.respond_to?(:associative?) && type.associative?
29
+ build_association_search(attr, type)
30
+ else
31
+ text_attribute(scoped_resource.model, attr).matches(search_pattern, LIKE_ESCAPE)
32
+ end
33
+ end
34
+ .compact
38
35
 
39
36
  return scoped_resource if conditions.empty?
40
37
 
41
38
  combined = conditions.reduce(:or)
42
- results = scoped_resource.where(combined)
43
- @association_search_joined ? results.distinct : results
39
+ scoped_resource.where(combined)
44
40
  end
45
41
 
46
42
  def build_association_search(attr, type)
47
43
  reflection = scoped_resource.model.reflect_on_association(attr)
48
44
  return nil unless reflection
49
45
 
50
- assoc_table = reflection.klass.arel_table
51
46
  columns = association_search_columns(type, reflection.klass)
52
47
  return nil if columns.empty?
53
48
 
54
- @scoped_resource = scoped_resource.left_joins(attr)
55
- @association_search_joined = true
56
- columns
57
- .map { |column| assoc_table[column].matches(search_pattern, LIKE_ESCAPE) }
58
- .reduce(:or)
49
+ condition =
50
+ columns
51
+ .map { |column| text_attribute(reflection.klass, column).matches(search_pattern, LIKE_ESCAPE) }
52
+ .reduce(:or)
53
+
54
+ # Match IDs so association joins do not duplicate rows or compare JSON columns.
55
+ model = scoped_resource.model
56
+ primary_key = model.arel_table[model.primary_key]
57
+ ids = model.unscoped.joins(attr).where(condition).reselect(primary_key)
58
+ primary_key.in(ids.arel)
59
59
  end
60
60
 
61
61
  def association_search_columns(type, associated_class)
62
62
  configured = type.respond_to?(:options) ? Array(type.options[:searchable_fields]) : []
63
- candidates = configured.presence || [:name, :title, :email]
64
- column_names = associated_class.column_names
63
+ candidates = configured.presence || %i[name title email]
64
+ column_names = associated_class.column_names + associated_class.stored_attributes.values.flatten.map(&:to_s)
65
65
 
66
- candidates
67
- .map(&:to_s)
68
- .select { |column| column_names.include?(column) }
69
- .map(&:to_sym)
66
+ candidates.map(&:to_s).select { |column| column_names.include?(column) }.map(&:to_sym)
67
+ end
68
+
69
+ def text_attribute(model, attribute)
70
+ mysql = %w[Mysql2 Trilogy].include?(model.connection.adapter_name)
71
+ store = model.stored_attributes.find { |_column, fields| fields.include?(attribute.to_sym) }
72
+ expression =
73
+ if store
74
+ column = model.arel_table[store.first]
75
+ if mysql
76
+ path = Arel::Nodes.build_quoted("$.#{attribute.to_s.to_json}")
77
+ value = Arel::Nodes::NamedFunction.new("JSON_EXTRACT", [column, path])
78
+ Arel::Nodes::NamedFunction.new("JSON_UNQUOTE", [value])
79
+ else
80
+ Arel::Nodes::InfixOperation.new("->>", column, Arel::Nodes.build_quoted(attribute.to_s))
81
+ end
82
+ else
83
+ model.arel_table[attribute]
84
+ end
85
+ model.arel_table.cast(expression, mysql ? "CHAR" : "text")
70
86
  end
71
87
 
72
88
  def search_pattern
@@ -1,3 +1,3 @@
1
1
  module Terrazzo
2
- VERSION = "0.7.3"
2
+ VERSION = "0.7.5"
3
3
  end
data/terrazzo.gemspec CHANGED
@@ -31,6 +31,9 @@ Gem::Specification.new do |spec|
31
31
  spec.require_paths = ["lib"]
32
32
 
33
33
  spec.add_dependency "rails", ">= 7.1"
34
+ # Current Rails releases require the JSON 2 argument format.
35
+ # Remove this limit after released Rails versions include rails/rails#58601.
36
+ spec.add_dependency "json", "~> 2.0"
34
37
  spec.add_dependency "kaminari", "~> 1.2"
35
38
  spec.add_dependency "superglue", "~> 1.1"
36
39
  spec.add_dependency "form_props", ">= 0.0.6"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terrazzo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Terrazzo Contributors
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: kaminari
28
42
  requirement: !ruby/object:Gem::Requirement