tramway 4.0 → 4.0.1.1
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 +4 -4
- data/README.md +58 -2
- data/app/assets/javascripts/tramway/tramway.js +33 -5
- data/app/components/tramway/navbar_component.html.haml +35 -0
- data/app/controllers/tramway/entities_controller.rb +7 -0
- data/app/views/tramway/entities/_list.html.haml +2 -1
- data/lib/generators/tramway/install/install_generator.rb +2 -1
- data/lib/tramway/configs/entities/page.rb +1 -0
- data/lib/tramway/errors.rb +45 -0
- data/lib/tramway/pg_searchable.rb +62 -0
- data/lib/tramway/searchable.rb +8 -26
- data/lib/tramway/version.rb +1 -1
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7d9ecf679835522a59184b0ffdbc6edf5cfa66aa5f1126429828497b3115e12
|
|
4
|
+
data.tar.gz: efa55f1a6971b64000f41f387b6a85b7d41756b4c5240c35ce3f283f6f878f4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67ac2fd8508f6a2d172638f3ac7f808721e5d4c1aa24f13e247238196c1fc90cdc5ddc1e0f7d5aa41cf13c1e47d0fa0d118d807f073afa1372ebc83b9b0f873b
|
|
7
|
+
data.tar.gz: 2bd0d71dbade50601a643f2c6c3567b00440d2104db60f53fc0f1a3937c73c77fde8509a8cddea9b68c908dfe92214c036e6b14bee02919c9a1a0b2f4ef5b1be
|
data/README.md
CHANGED
|
@@ -219,8 +219,47 @@ Tramway.configure do |config|
|
|
|
219
219
|
end
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
-
When search is enabled,
|
|
223
|
-
|
|
222
|
+
When search is enabled, a text input and a search icon button appear at the top of the index page. Tramway uses
|
|
223
|
+
`Model.search(query)` if you define it (e.g. a `pg_search_scope` or any custom scope named `search`). If not, it falls back
|
|
224
|
+
to `Model.tramway_search(query)`, which searches across all string/text columns of the model using the [`pg_search`](https://github.com/Casecommons/pg_search)
|
|
225
|
+
gem, and logs a warning. The fallback is generic and not tailored to your data structure, so it is not intended for
|
|
226
|
+
long-term use and may be slow or not scalable.
|
|
227
|
+
|
|
228
|
+
`pg_search` requires PostgreSQL's full-text search functions. If your application's database is not PostgreSQL, calling
|
|
229
|
+
the fallback raises `Tramway::Errors::UnsupportedDatabaseAdapterError` with an explanation and a suggestion to define your
|
|
230
|
+
own `Model.search(query)` scope instead.
|
|
231
|
+
|
|
232
|
+
`bin/rails g tramway:install` adds `gem "pg_search"` to your Gemfile automatically (like Tramway's other dependencies), so
|
|
233
|
+
run it (and `bundle install`) if you see the fallback raise `Tramway::Errors::MissingGemError`. That error also fires if
|
|
234
|
+
`pg_search` can't otherwise be loaded, and explains how to fix it or define your own `Model.search(query)` scope.
|
|
235
|
+
|
|
236
|
+
**includes**
|
|
237
|
+
|
|
238
|
+
If a decorator's `index_attributes` reads an association (e.g. `object.comments.map(&:text)`), each row triggers its own
|
|
239
|
+
query unless the association is eager-loaded. Set `includes` on the `:index` page entry to preload it, using the same
|
|
240
|
+
shape Rails' `.includes` accepts (a symbol, array, or nested hash):
|
|
241
|
+
|
|
242
|
+
*config/initializers/tramway.rb*
|
|
243
|
+
```ruby
|
|
244
|
+
Tramway.configure do |config|
|
|
245
|
+
config.entities = [
|
|
246
|
+
{
|
|
247
|
+
name: :campaign,
|
|
248
|
+
namespace: :admin,
|
|
249
|
+
pages: [
|
|
250
|
+
{
|
|
251
|
+
action: :index,
|
|
252
|
+
includes: [:user, :comments]
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
In this example, Tramway calls `Campaign.includes(:user, :comments)` before applying `scope`/`search`, so accessing
|
|
261
|
+
`campaign.user` or `campaign.comments` in the decorator's `index_attributes` does not issue an extra query per row.
|
|
262
|
+
Omitting `includes` produces identical behavior to before this option existed.
|
|
224
263
|
|
|
225
264
|
**show page**
|
|
226
265
|
|
|
@@ -946,6 +985,23 @@ renders the classic top navbar, and desktop `:vertical` renders the collapsible
|
|
|
946
985
|
When `tramway_navbar` renders before `tramway_main_container`, the main container helper keeps the vertical
|
|
947
986
|
sidebar offset in sync automatically and removes it for horizontal pages.
|
|
948
987
|
|
|
988
|
+
The collapsed/expanded state of the vertical sidebar persists across page navigations. The Stimulus controller
|
|
989
|
+
saves the state to the browser's `localStorage` (key `tramway-navbar-expanded`) whenever the toggle button is
|
|
990
|
+
used, and reads it back on every page load, so collapsing the sidebar on one page keeps it collapsed everywhere
|
|
991
|
+
else until it's expanded again. This is per-browser state: it isn't synced across devices or persisted server-side,
|
|
992
|
+
and if `localStorage` is unavailable (e.g. private browsing with storage blocked), the sidebar simply falls back
|
|
993
|
+
to always starting expanded.
|
|
994
|
+
|
|
995
|
+
Since the server always renders the sidebar expanded (it has no way to know the browser's stored preference) and
|
|
996
|
+
the Stimulus controller only applies the collapsed classes once it connects, `tramway_navbar` also renders a small
|
|
997
|
+
blocking inline `<script>` alongside its markup. It reads `localStorage` synchronously, before the browser paints,
|
|
998
|
+
and — if the sidebar was left collapsed — sets a `data-tramway-navbar-expanded="false"` attribute on `<html>`. A
|
|
999
|
+
matching `@media (min-width: 768px)` CSS block (rendered alongside the navbar) keys off that attribute to apply the
|
|
1000
|
+
collapsed width, main-container padding, and faded header/menu immediately, before any JavaScript module has had a
|
|
1001
|
+
chance to load and run. This is what prevents the sidebar from flashing expanded and then snapping to collapsed on
|
|
1002
|
+
page load. The Stimulus controller keeps that `<html>` attribute in sync afterwards (on load and on every toggle),
|
|
1003
|
+
so this pre-paint styling and the controller's own classes never fight each other.
|
|
1004
|
+
|
|
949
1005
|
**NOTE:** `tramway_navbar` method called without arguments and block of code will render only [Tramway CRUD](https://github.com/Purple-Magic/tramway#tramway-crud) links on the left.
|
|
950
1006
|
|
|
951
1007
|
In case you want to hide entity links you can pass `with_entities: false`.
|
|
@@ -467,6 +467,7 @@ class Navbar extends Controller {
|
|
|
467
467
|
this.desktopMainExpandedPaddingClass = 'md:pl-72'
|
|
468
468
|
this.desktopMainCollapsedPaddingClass = 'md:pl-24'
|
|
469
469
|
this.hiddenInteractionClasses = ['opacity-0', 'pointer-events-none']
|
|
470
|
+
this.expandedStorageKey = 'tramway-navbar-expanded'
|
|
470
471
|
|
|
471
472
|
this.desktopNavbar = this.element
|
|
472
473
|
this.desktopHeader = document.getElementById('desktop-navbar-header')
|
|
@@ -490,8 +491,22 @@ class Navbar extends Controller {
|
|
|
490
491
|
document.addEventListener('turbo:before-cache', this.handleBeforeCache)
|
|
491
492
|
window.addEventListener('resize', this.handleResize)
|
|
492
493
|
|
|
493
|
-
|
|
494
|
-
|
|
494
|
+
this.syncDesktopExpandedState()
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
readStoredExpanded() {
|
|
498
|
+
try {
|
|
499
|
+
return window.localStorage.getItem(this.expandedStorageKey)
|
|
500
|
+
} catch {
|
|
501
|
+
return null
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
writeStoredExpanded(expanded) {
|
|
506
|
+
try {
|
|
507
|
+
window.localStorage.setItem(this.expandedStorageKey, expanded ? 'true' : 'false')
|
|
508
|
+
} catch {
|
|
509
|
+
/* localStorage unavailable (e.g. private mode) — state simply won't persist */
|
|
495
510
|
}
|
|
496
511
|
}
|
|
497
512
|
|
|
@@ -579,18 +594,31 @@ class Navbar extends Controller {
|
|
|
579
594
|
}
|
|
580
595
|
|
|
581
596
|
toggleDesktopExpanded() {
|
|
582
|
-
this.setDesktopExpanded(this.desktopNavbar.dataset.expanded === 'false')
|
|
597
|
+
this.setDesktopExpanded(this.desktopNavbar.dataset.expanded === 'false', { persist: true })
|
|
583
598
|
}
|
|
584
599
|
|
|
585
600
|
syncDesktopExpandedState() {
|
|
586
|
-
this.
|
|
601
|
+
const storedExpanded = this.readStoredExpanded()
|
|
602
|
+
const expanded = storedExpanded === null ? this.desktopNavbar.dataset.expanded !== 'false' : storedExpanded !== 'false'
|
|
603
|
+
|
|
604
|
+
this.setDesktopExpanded(expanded)
|
|
587
605
|
}
|
|
588
606
|
|
|
589
|
-
setDesktopExpanded(expanded) {
|
|
607
|
+
setDesktopExpanded(expanded, { persist = false } = {}) {
|
|
590
608
|
if (!this.isVertical() || !this.desktopNavbar || !this.desktopHeader || !this.desktopMenu || !this.desktopMainContainer || !this.desktopToggleButton || !this.desktopToggleWrapper) {
|
|
591
609
|
return
|
|
592
610
|
}
|
|
593
611
|
|
|
612
|
+
if (persist) {
|
|
613
|
+
this.writeStoredExpanded(expanded)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (expanded) {
|
|
617
|
+
document.documentElement.removeAttribute('data-tramway-navbar-expanded')
|
|
618
|
+
} else {
|
|
619
|
+
document.documentElement.setAttribute('data-tramway-navbar-expanded', 'false')
|
|
620
|
+
}
|
|
621
|
+
|
|
594
622
|
this.desktopNavbar.classList.toggle(this.desktopNavbarExpandedWidthClass, expanded)
|
|
595
623
|
this.desktopNavbar.classList.toggle(this.desktopNavbarCollapsedWidthClass, !expanded)
|
|
596
624
|
this.desktopNavbar.dataset.expanded = expanded ? 'true' : 'false'
|
|
@@ -102,6 +102,17 @@
|
|
|
102
102
|
Close
|
|
103
103
|
|
|
104
104
|
- if vertical?
|
|
105
|
+
:javascript
|
|
106
|
+
(function () {
|
|
107
|
+
try {
|
|
108
|
+
if (window.localStorage.getItem('tramway-navbar-expanded') === 'false') {
|
|
109
|
+
document.documentElement.setAttribute('data-tramway-navbar-expanded', 'false');
|
|
110
|
+
} else {
|
|
111
|
+
document.documentElement.removeAttribute('data-tramway-navbar-expanded');
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {}
|
|
114
|
+
})();
|
|
115
|
+
|
|
105
116
|
:css
|
|
106
117
|
.tramway-navbar-desktop-vertical .tramway-navbar-item {
|
|
107
118
|
width: 100%;
|
|
@@ -121,3 +132,27 @@
|
|
|
121
132
|
width: 100%;
|
|
122
133
|
justify-content: flex-start;
|
|
123
134
|
}
|
|
135
|
+
|
|
136
|
+
@media (min-width: 768px) {
|
|
137
|
+
html[data-tramway-navbar-expanded="false"] #desktop-navbar {
|
|
138
|
+
width: 6rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
html[data-tramway-navbar-expanded="false"] #tramway-main-container {
|
|
142
|
+
padding-left: 6rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
html[data-tramway-navbar-expanded="false"] #desktop-navbar-header,
|
|
146
|
+
html[data-tramway-navbar-expanded="false"] #desktop-navbar-content {
|
|
147
|
+
opacity: 0;
|
|
148
|
+
pointer-events: none;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
html[data-tramway-navbar-expanded="false"] #desktop-navbar-toggle-wrapper {
|
|
152
|
+
justify-content: center;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
html[data-tramway-navbar-expanded="false"] #desktop-navbar-toggle-icon {
|
|
156
|
+
transform: rotate(180deg);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -19,6 +19,7 @@ module Tramway
|
|
|
19
19
|
model_class.order(id: :desc)
|
|
20
20
|
end => entities
|
|
21
21
|
|
|
22
|
+
entities = preload(entities)
|
|
22
23
|
entities = search(entities)
|
|
23
24
|
entities = entities.page(params[:page])
|
|
24
25
|
@entities = entities
|
|
@@ -90,6 +91,12 @@ module Tramway
|
|
|
90
91
|
entity.page(:index).scope
|
|
91
92
|
end
|
|
92
93
|
|
|
94
|
+
def preload(entities)
|
|
95
|
+
includes = entity.page(:index).includes
|
|
96
|
+
|
|
97
|
+
includes.present? ? entities.includes(includes) : entities
|
|
98
|
+
end
|
|
99
|
+
|
|
93
100
|
def set_associations
|
|
94
101
|
@associations = @record.send(:__show_associations, params[:page])
|
|
95
102
|
end
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
- if @entity.page(:index).search
|
|
23
23
|
= form_with url: public_send(custom_path_method), method: :get, local: true, builder: Tramway::Form::Builder, horizontal: true do |f|
|
|
24
24
|
= f.text_field :query, label: false, value: params[:query], placeholder: t('tramway.actions.search')
|
|
25
|
-
= f.submit t('tramway.actions.search')
|
|
25
|
+
= f.submit nil, 'aria-label' => t('tramway.actions.search') do
|
|
26
|
+
%i.fa.fa-search{ 'aria-hidden' => 'true' }
|
|
26
27
|
|
|
27
28
|
= paginate @entities, custom_path_method:
|
|
28
29
|
|
|
@@ -51,7 +51,8 @@ module Tramway
|
|
|
51
51
|
{ name: 'kaminari', declaration: 'gem "kaminari"' },
|
|
52
52
|
{ name: 'view_component', declaration: 'gem "view_component"' },
|
|
53
53
|
{ name: 'dry-initializer', declaration: "gem 'dry-initializer'" },
|
|
54
|
-
{ name: 'dry-monads', declaration: "gem 'dry-monads'" }
|
|
54
|
+
{ name: 'dry-monads', declaration: "gem 'dry-monads'" },
|
|
55
|
+
{ name: 'pg_search', declaration: "gem 'pg_search'" }
|
|
55
56
|
]
|
|
56
57
|
end
|
|
57
58
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
# Namespace for Tramway-specific error classes
|
|
5
|
+
module Errors
|
|
6
|
+
# Raised when a Tramway feature depends on infrastructure (e.g. a specific
|
|
7
|
+
# database adapter) that the current application does not provide, so the
|
|
8
|
+
# developer gets an actionable error instead of a raw, confusing failure
|
|
9
|
+
# deep inside the underlying gem.
|
|
10
|
+
class UnsupportedDatabaseAdapterError < StandardError
|
|
11
|
+
def initialize(feature:, model_class:, adapter:, required_adapter: 'PostgreSQL')
|
|
12
|
+
super(<<~MESSAGE)
|
|
13
|
+
Tramway #{feature} requires #{required_adapter}, but #{model_class} is connected \
|
|
14
|
+
through the "#{adapter}" adapter.
|
|
15
|
+
|
|
16
|
+
This feature relies on the `pg_search` gem, which needs PostgreSQL's full-text \
|
|
17
|
+
search functions (to_tsvector/to_tsquery) and does not work on other adapters.
|
|
18
|
+
|
|
19
|
+
To fix this:
|
|
20
|
+
- switch #{model_class}'s database connection to PostgreSQL, or
|
|
21
|
+
- define a custom `#{model_class}.search(query)` scope that works with your \
|
|
22
|
+
adapter; Tramway will use it instead of the built-in pg_search fallback.
|
|
23
|
+
MESSAGE
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Raised when a Tramway feature depends on a gem that could not be loaded or used, so the
|
|
28
|
+
# developer (or an AI agent) gets a precise, actionable fix instead of a raw NoMethodError
|
|
29
|
+
# or LoadError deep inside the missing gem's call chain.
|
|
30
|
+
class MissingGemError < StandardError
|
|
31
|
+
def initialize(feature:, model_class:, gem_name:, original_error:)
|
|
32
|
+
super(<<~MESSAGE)
|
|
33
|
+
Tramway #{feature} for #{model_class} could not use the `#{gem_name}` gem \
|
|
34
|
+
(#{original_error.class}: #{original_error.message}).
|
|
35
|
+
|
|
36
|
+
To fix this:
|
|
37
|
+
- add `gem "#{gem_name}"` to your Gemfile, run `bundle install`, and restart your \
|
|
38
|
+
server (or re-run `bin/rails g tramway:install`, which adds it automatically), or
|
|
39
|
+
- define a custom `#{model_class}.search(query)` scope that does not depend on \
|
|
40
|
+
`#{gem_name}`; Tramway will use it instead of the built-in fallback.
|
|
41
|
+
MESSAGE
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tramway/errors'
|
|
4
|
+
|
|
5
|
+
module Tramway
|
|
6
|
+
# Wires up a default, generic search across all string/text columns using the
|
|
7
|
+
# `pg_search` gem, so entities#index search works out of the box for any model
|
|
8
|
+
# that does not define its own `.search` scope.
|
|
9
|
+
#
|
|
10
|
+
# This is only supported on PostgreSQL, since pg_search depends on PostgreSQL's
|
|
11
|
+
# full-text search functions. On any other adapter it raises a clear,
|
|
12
|
+
# actionable error instead of failing with a confusing SQL error deep inside
|
|
13
|
+
# pg_search.
|
|
14
|
+
module PgSearchable
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def call(model_class, query)
|
|
18
|
+
ensure_postgres!(model_class)
|
|
19
|
+
ensure_pg_search_scope!(model_class)
|
|
20
|
+
|
|
21
|
+
model_class.tramway_pg_search(query)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ensure_postgres!(model_class)
|
|
25
|
+
adapter = model_class.connection.adapter_name
|
|
26
|
+
|
|
27
|
+
return if adapter.casecmp('postgresql').zero?
|
|
28
|
+
|
|
29
|
+
raise Tramway::Errors::UnsupportedDatabaseAdapterError.new(
|
|
30
|
+
feature: 'the default entities#index search',
|
|
31
|
+
model_class:,
|
|
32
|
+
adapter:
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ensure_pg_search_scope!(model_class)
|
|
37
|
+
return if model_class.respond_to?(:tramway_pg_search)
|
|
38
|
+
|
|
39
|
+
define_pg_search_scope!(model_class)
|
|
40
|
+
rescue LoadError, NameError => e
|
|
41
|
+
raise missing_pg_search_gem_error(model_class, e)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def define_pg_search_scope!(model_class)
|
|
45
|
+
require 'pg_search'
|
|
46
|
+
|
|
47
|
+
model_class.include(PgSearch::Model)
|
|
48
|
+
model_class.pg_search_scope :tramway_pg_search,
|
|
49
|
+
against: model_class.searchable_column_names,
|
|
50
|
+
using: { tsearch: { prefix: true } }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def missing_pg_search_gem_error(model_class, original_error)
|
|
54
|
+
Tramway::Errors::MissingGemError.new(
|
|
55
|
+
feature: 'the default entities#index search',
|
|
56
|
+
model_class:,
|
|
57
|
+
gem_name: 'pg_search',
|
|
58
|
+
original_error:
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/tramway/searchable.rb
CHANGED
|
@@ -1,43 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'tramway/pg_searchable'
|
|
4
|
+
|
|
3
5
|
module Tramway
|
|
4
|
-
# Searchable module provides a class method `tramway_search` for ActiveRecord models to
|
|
5
|
-
# across all string and text columns using
|
|
6
|
+
# Searchable module provides a class method `tramway_search` for ActiveRecord models to
|
|
7
|
+
# perform a generic search across all string and text columns, using the `pg_search` gem.
|
|
8
|
+
# It is the fallback Tramway::EntitiesController uses when a model does not define its
|
|
9
|
+
# own `.search` scope. See Tramway::PgSearchable for the PostgreSQL requirement.
|
|
6
10
|
module Searchable
|
|
7
11
|
extend ActiveSupport::Concern
|
|
8
12
|
|
|
9
13
|
class_methods do
|
|
10
14
|
def tramway_search(query)
|
|
11
|
-
|
|
12
|
-
columns_to_search = searchable_column_names
|
|
13
|
-
|
|
14
|
-
return all if tokens.empty? || columns_to_search.empty?
|
|
15
|
-
|
|
16
|
-
where(
|
|
17
|
-
Arel.sql("to_tsvector('simple', #{tsvector_expression(columns_to_search)}) @@ to_tsquery('simple', ?)"),
|
|
18
|
-
tsquery_expression(tokens)
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
15
|
+
return all if query.blank?
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
query.to_s.scan(/[[:alnum:]]+/)
|
|
17
|
+
Tramway::PgSearchable.call(self, query)
|
|
26
18
|
end
|
|
27
19
|
|
|
28
20
|
def searchable_column_names
|
|
29
21
|
columns.select { |column| column.type.in?(%i[string text]) }.map(&:name)
|
|
30
22
|
end
|
|
31
|
-
|
|
32
|
-
def tsvector_expression(column_names)
|
|
33
|
-
column_names.map do |column_name|
|
|
34
|
-
"coalesce(#{connection.quote_column_name(column_name)}, '')"
|
|
35
|
-
end.join(" || ' ' || ")
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def tsquery_expression(tokens)
|
|
39
|
-
tokens.map { |token| "#{token}:*" }.join(' & ')
|
|
40
|
-
end
|
|
41
23
|
end
|
|
42
24
|
end
|
|
43
25
|
end
|
data/lib/tramway/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tramway
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kalashnikovisme
|
|
@@ -94,6 +94,20 @@ dependencies:
|
|
|
94
94
|
- - ">="
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: pg_search
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
97
111
|
- !ruby/object:Gem::Dependency
|
|
98
112
|
name: rails
|
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -309,6 +323,7 @@ files:
|
|
|
309
323
|
- lib/tramway/duck_typing.rb
|
|
310
324
|
- lib/tramway/duck_typing/active_record_compatibility.rb
|
|
311
325
|
- lib/tramway/engine.rb
|
|
326
|
+
- lib/tramway/errors.rb
|
|
312
327
|
- lib/tramway/forms/class_helper.rb
|
|
313
328
|
- lib/tramway/forms/fields.rb
|
|
314
329
|
- lib/tramway/forms/normalizations.rb
|
|
@@ -322,6 +337,7 @@ files:
|
|
|
322
337
|
- lib/tramway/helpers/table_helper.rb
|
|
323
338
|
- lib/tramway/helpers/views_helper.rb
|
|
324
339
|
- lib/tramway/navbar.rb
|
|
340
|
+
- lib/tramway/pg_searchable.rb
|
|
325
341
|
- lib/tramway/searchable.rb
|
|
326
342
|
- lib/tramway/utils/field.rb
|
|
327
343
|
- lib/tramway/utils/render.rb
|