terrazzo 0.7.2 → 0.7.4

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: 0fa314f0163e8980b8d9008128b2b1e2d365aae6c3dfdbf318f225f76ddb75dd
4
- data.tar.gz: c4537807333d2d028706e5ccd8bc0bdc74141c3432cd32b651170cce479a10b0
3
+ metadata.gz: 69d154e835cecd712223cce10063d1e51a185dbe26c705acab6b9f040d4dd0b8
4
+ data.tar.gz: ef0f793e70ca95e739eefc40b64b3188ae1774fb3338489316136fc19b08f746
5
5
  SHA512:
6
- metadata.gz: 466b4ed0ea5ef20ccbad8d9813780656486858c6848925c5dc576a67f400924b4eb3675d6d70ffcd4ac4747fbaf789c099e8a7ad3ddd7150dbd70d9800191266
7
- data.tar.gz: c6ca0e192e3d578a9dc32e2b33fba3c7f4c33e0db9a42e80ae1d8ef7fd68c90c61a4e73572d9c61358cb59eb998442a98d39c1f0e7c407f7119af538c8852274
6
+ metadata.gz: 11217eadfa4391055dec08ea2958cabdc10954ac653b027a14130e632ff0c7e5643657ca486c0096909014ec9276ea80909036e3aa267408d71604839f0bc9cd
7
+ data.tar.gz: ad45007e3d20d3d32e26939e46cf58ad62245c240e49ab9f2e0d9293252f18dcdde30e6873237cbc5b31ce90a0c85fb0442cfcbb8fc73c151a86699975e50c28
data/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  A drop-in admin panel for Rails apps. Uses the [Administrate](https://github.com/thoughtbot/administrate) dashboard DSL with a React SPA frontend powered by [Superglue](https://github.com/thoughtbot/superglue).
4
4
 
5
+ [![CI](https://github.com/gohypelab/terrazzo/actions/workflows/ci.yml/badge.svg)](https://github.com/gohypelab/terrazzo/actions/workflows/ci.yml)
6
+ [![Release](https://github.com/gohypelab/terrazzo/actions/workflows/release.yml/badge.svg)](https://github.com/gohypelab/terrazzo/actions/workflows/release.yml)
7
+
5
8
  - **Familiar DSL** — same `ATTRIBUTE_TYPES`, `COLLECTION_ATTRIBUTES`, `FORM_ATTRIBUTES` you already know
6
9
  - **React SPA** — search, sort, and paginate without full page reloads, no separate API needed
7
10
  - **shadcn/ui + Tailwind** — polished defaults with supported ejection when you want app-owned source
@@ -23,11 +26,13 @@ bundle exec vite install
23
26
 
24
27
  # Add the npm package and frontend dependencies
25
28
  npm install terrazzo
26
- npm install @radix-ui/react-avatar @radix-ui/react-dialog @radix-ui/react-dropdown-menu \
27
- @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select \
28
- @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-tooltip \
29
- class-variance-authority lucide-react tailwindcss@latest
30
- npm install --save-dev @tailwindcss/cli@latest # if your app does not already compile Tailwind
29
+ npm install @radix-ui/react-avatar@1.1.11 @radix-ui/react-dialog@1.1.15 \
30
+ @radix-ui/react-dropdown-menu@2.1.16 @radix-ui/react-label@2.1.8 \
31
+ @radix-ui/react-popover@1.1.15 @radix-ui/react-select@2.2.6 \
32
+ @radix-ui/react-separator@1.1.8 @radix-ui/react-slot@1.2.4 \
33
+ @radix-ui/react-tooltip@1.2.8 class-variance-authority@0.7.1 \
34
+ lucide-react@0.344.0 tailwindcss@4.2.1
35
+ npm install --save-dev @tailwindcss/cli@4.2.1 # if your app does not already compile Tailwind
31
36
 
32
37
  # Install Terrazzo — generates admin namespace, app barrels,
33
38
  # shadcn metadata/path aliases, and dashboards.
@@ -58,6 +63,39 @@ Full docs at **[gohypelab.github.io/terrazzo](https://gohypelab.github.io/terraz
58
63
  - Node.js 18+
59
64
  - A JS bundler (Vite recommended, esbuild also supported)
60
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
+
69
+ ## Development and releases
70
+
71
+ Pull requests and pushes to `main` run the Ruby unit specs, npm package checks,
72
+ documentation build, and example app system specs through GitHub Actions.
73
+
74
+ Repository npm projects pin direct dependencies and use a seven-day minimum
75
+ release age when resolving updates. The published package and documentation
76
+ projects also commit their lockfiles. Use npm 11.10 or newer when changing
77
+ dependencies; project `.npmrc` files save new dependencies with exact versions
78
+ automatically.
79
+
80
+ Terrazzo releases the Ruby gem and npm package from the same version tag. After
81
+ updating `lib/terrazzo/version.rb` and `npm/package.json` to matching versions,
82
+ push an annotated `v*` tag. The release workflow validates both packages,
83
+ publishes missing versions to RubyGems and npm through OIDC trusted publishing,
84
+ and creates a GitHub Release with generated notes. Existing registry versions
85
+ are skipped, so a partially completed or manually published release can be
86
+ retried safely.
87
+
88
+ See [RELEASING.md](RELEASING.md) for trusted publisher setup, release commands,
89
+ and retry instructions.
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
+
61
99
  ## Customizing Per-Row Actions
62
100
 
63
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
@@ -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.2"
2
+ VERSION = "0.7.4"
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.2
4
+ version: 0.7.4
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
@@ -339,7 +353,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
339
353
  - !ruby/object:Gem::Version
340
354
  version: '0'
341
355
  requirements: []
342
- rubygems_version: 4.0.3
356
+ rubygems_version: 3.6.9
343
357
  specification_version: 4
344
358
  summary: A Rails admin framework powered by Superglue and React
345
359
  test_files: []