tam_select 0.1.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3a066f1e145b6a880f28494f78c00d51c1ca280fc984975c2eebf849704041c
4
- data.tar.gz: 1537eeda1e3dda9a3a38077bbfe6076cc9d851e6b404345188b61b845fd3107a
3
+ metadata.gz: 3cdee3c229caaeda5d7f572395009c1ffd03e58c1f5376c215ced0e751c35fb9
4
+ data.tar.gz: 9896dd22aa1dc9af3727596c1bee1d82ca6b8f1462a3e24a5177b10306853888
5
5
  SHA512:
6
- metadata.gz: 454ff7b7e777da2409741dff572ead04fc84ae5e65be2984c44d9c0196924ecf6d695e7445d23cbb7ba683abea8bf7e821f79e06cac71da9e4c8c6a718f8c823
7
- data.tar.gz: f5950761f9e589454f4dad82e2ed52dbe5ac88d5856a8ee133da81916bf5e5ec20c8e434e23aca8a868b8d9cce4612d5926929e1ef4643e9c4d597541c3b943e
6
+ metadata.gz: 271d3b88383037c504189080665c0c15e19a640e6f8efe0d580683d9f543a0a56ff496a667064b788fbd8a8f1ddc8f48512d755cfc10985ec043d36c042891ce
7
+ data.tar.gz: be82081ad386749cb1ff40f1cd48ca80417104712c417de25dccc969082f3f2edfce09a912bce53467c34e170b1a0235245caede8d53717b7fff5d3b21d8f072
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
- # Tam Select
1
+ the # Tam Select
2
2
 
3
3
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![Gem Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](https://github.com/tamiru/tam_select)
4
5
 
5
6
  **Tam Select** is an accessible, searchable select component for Ruby on Rails, built for Simple Form, Stimulus, Turbo, and Tailwind CSS. It keeps the native `<select>` as the source of truth, so Rails form submission, validation, selected values, and browser autofill continue to work.
6
7
 
@@ -35,6 +36,7 @@ app/javascript/tam_select/tam_select.js
35
36
  app/javascript/controllers/tam_select_controller.js
36
37
  app/inputs/tam_select_input.rb
37
38
  app/controllers/concerns/tam_select_paginatable.rb
39
+ app/controllers/concerns/tam_select_remote.rb
38
40
  app/controllers/tam_select_remote_controller.rb
39
41
  app/helpers/tam_select_helper.rb
40
42
  ```
@@ -48,6 +50,23 @@ Add the generated JavaScript to Tailwind's source detection in `app/assets/tailw
48
50
  @source "../../javascript/tam_select/**/*.js";
49
51
  ```
50
52
 
53
+ ### Updating
54
+
55
+ The Rails integration files are copied into your application, so updating the gem does not update them automatically. Commit local customizations first, then run:
56
+
57
+ ```bash
58
+ bundle update tam_select
59
+ bin/rails generate tam_select:install
60
+ ```
61
+
62
+ Rails prompts before replacing changed files. To replace every generated file without prompting, use:
63
+
64
+ ```bash
65
+ bin/rails generate tam_select:install --force
66
+ ```
67
+
68
+ Review `git diff` afterward because `--force` overwrites application-specific customizations.
69
+
51
70
  ## Features
52
71
 
53
72
  - Single and multiple selection
@@ -113,7 +132,9 @@ Stimulus destroys generated markup when Turbo removes the select and recreates i
113
132
 
114
133
  ## Simple Form
115
134
 
116
- The install generator creates `app/inputs/tam_select_input.rb`. Use it like any other Simple Form input:
135
+ The install generator creates `app/inputs/tam_select_input.rb`. Use `as: :tam_select` with any Simple Form collection input.
136
+
137
+ ### Local collection
117
138
 
118
139
  ```erb
119
140
  <%= form.input :region_id,
@@ -121,16 +142,49 @@ The install generator creates `app/inputs/tam_select_input.rb`. Use it like any
121
142
  collection: Region.order(:name),
122
143
  label_method: :name,
123
144
  value_method: :id,
145
+ prompt: "Select region" %>
146
+ ```
147
+
148
+ ### Remote collection
149
+
150
+ Keep the currently selected record in the initial collection so edit forms can display its label before the AJAX request completes:
151
+
152
+ ```erb
153
+ <%= form.input :region_id,
154
+ as: :tam_select,
155
+ collection: [form.object.region].compact,
156
+ label_method: :name,
157
+ value_method: :id,
124
158
  prompt: "Select region",
125
159
  input_html: {
126
160
  tam_options: {
127
- remoteUrl: regions_path,
128
- minQueryLength: 1
161
+ remoteUrl: tam_select_options_regions_path(format: :json),
162
+ minQueryLength: 1,
163
+ debounce: 250
164
+ }
165
+ } %>
166
+ ```
167
+
168
+ The concern and route required by this example are described in [Add remote search to an existing controller](#add-remote-search-to-an-existing-controller).
169
+
170
+ ### Multiple selection
171
+
172
+ ```erb
173
+ <%= form.input :skill_ids,
174
+ as: :tam_select,
175
+ collection: Skill.order(:name),
176
+ label_method: :name,
177
+ value_method: :id,
178
+ input_html: {
179
+ multiple: true,
180
+ tam_options: {
181
+ searchable: true,
182
+ closeAfterSelect: false
129
183
  }
130
184
  } %>
131
185
  ```
132
186
 
133
- For a many-to-many field, add `multiple: true` to `input_html`.
187
+ Options are passed under `input_html[:tam_options]`. Common options include `searchable`, `creatable`, `clearable`, `placeholder`, `searchPlaceholder`, `remoteUrl`, `minQueryLength`, and `debounce`. See [Main options](#main-options) for defaults.
134
188
 
135
189
  ## Remote API contract
136
190
 
@@ -154,6 +208,53 @@ The generator installs `TamSelectPaginatable` as a Rails response helper. A comp
154
208
 
155
209
  Secure remote endpoints exactly like other Rails JSON endpoints. Scope records by the current user's permissions and never trust a submitted value merely because it appeared in the dropdown.
156
210
 
211
+ ### Add remote search to an existing controller
212
+
213
+ Include `TamSelectRemote` and declare the allowed model and searchable fields. This adds the public `tam_select_options` action to the controller:
214
+
215
+ ```ruby
216
+ # app/controllers/regions_controller.rb
217
+ class RegionsController < ApplicationController
218
+ include TamSelectRemote
219
+
220
+ tam_select_remote(
221
+ model: Region,
222
+ label: :name,
223
+ value: :id,
224
+ search_by: %i[name code],
225
+ scope: -> { Region.order(:name) },
226
+ per_page: 20
227
+ )
228
+ end
229
+ ```
230
+
231
+ The optional `scope` lambda runs in the controller context, so it can use `current_user`, `current_account`, or an authorization policy.
232
+
233
+ ```ruby
234
+ # config/routes.rb
235
+ resources :regions do
236
+ get :tam_select_options, on: :collection, defaults: { format: :json }
237
+ end
238
+ ```
239
+
240
+ Point Simple Form to that collection action:
241
+
242
+ ```erb
243
+ <%= form.input :region_id,
244
+ as: :tam_select,
245
+ collection: [form.object.region].compact,
246
+ label_method: :name,
247
+ value_method: :id,
248
+ input_html: {
249
+ tam_options: {
250
+ remoteUrl: tam_select_options_regions_path(format: :json),
251
+ minQueryLength: 1
252
+ }
253
+ } %>
254
+ ```
255
+
256
+ Typing sends `GET /regions/tam_select_options.json?q=addis&page=1` and receives the standard Tam Select JSON payload.
257
+
157
258
  ### Generic remote controller
158
259
 
159
260
  The installer generates `TamSelectRemoteController`. Create a small subclass for each allowed remote source:
@@ -187,7 +288,7 @@ get "region_options", to: "region_options#index", defaults: { format: :json }
187
288
  value_method: :id,
188
289
  input_html: {
189
290
  tam_options: {
190
- remoteUrl: region_options_path,
291
+ remoteUrl: region_options_path(format: :json),
191
292
  minQueryLength: 1
192
293
  }
193
294
  } %>
@@ -195,6 +296,22 @@ get "region_options", to: "region_options#index", defaults: { format: :json }
195
296
 
196
297
  Do not accept a model name from request parameters. Declaring each source in a controller subclass prevents clients from querying arbitrary application models.
197
298
 
299
+ The browser sends requests such as:
300
+
301
+ ```text
302
+ GET /region_options.json?q=addis&page=1
303
+ Accept: application/json
304
+ ```
305
+
306
+ Test an endpoint independently with:
307
+
308
+ ```bash
309
+ curl -H "Accept: application/json" \
310
+ "http://localhost:3000/region_options.json?q=addis&page=1"
311
+ ```
312
+
313
+ A `406 Not Acceptable` response means the route or controller rejected JSON. Keep `defaults: { format: :json }` on the route, use a `.json` URL, and ensure the controller does not restrict responses to HTML only.
314
+
198
315
  ## Core JavaScript
199
316
 
200
317
  ```js
@@ -18,6 +18,7 @@ module TamSelect
18
18
 
19
19
  def copy_controller_concern
20
20
  copy_file "lib/generators/tam_select/templates/tam_select_paginatable.rb", "app/controllers/concerns/tam_select_paginatable.rb"
21
+ copy_file "lib/generators/tam_select/templates/tam_select_remote.rb", "app/controllers/concerns/tam_select_remote.rb"
21
22
  end
22
23
 
23
24
  def copy_remote_controller
@@ -0,0 +1,54 @@
1
+ module TamSelectRemote
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include TamSelectPaginatable
6
+ class_attribute :tam_select_remote_config, instance_writer: false
7
+ end
8
+
9
+ class_methods do
10
+ def tam_select_remote(model:, label:, value: :id, search_by: nil, scope: nil, per_page: 20)
11
+ self.tam_select_remote_config = {
12
+ model: model,
13
+ label: label,
14
+ value: value,
15
+ search_by: Array(search_by || label),
16
+ scope: scope,
17
+ per_page: per_page
18
+ }.freeze
19
+ end
20
+ end
21
+
22
+ def tam_select_options
23
+ config = tam_select_remote_config!
24
+ records = tam_select_remote_search(tam_select_remote_scope(config), config)
25
+
26
+ render json: tam_select_payload(
27
+ records,
28
+ label: config[:label],
29
+ value: config[:value],
30
+ per_page: config[:per_page]
31
+ )
32
+ end
33
+
34
+ private
35
+
36
+ def tam_select_remote_config!
37
+ self.class.tam_select_remote_config ||
38
+ raise("Configure #{self.class.name} with tam_select_remote(...)")
39
+ end
40
+
41
+ def tam_select_remote_scope(config)
42
+ config[:scope] ? instance_exec(&config[:scope]) : config[:model].all
43
+ end
44
+
45
+ def tam_select_remote_search(records, config)
46
+ query = params[:q].to_s.strip
47
+ return records if query.blank?
48
+
49
+ model = config[:model]
50
+ pattern = "%#{model.sanitize_sql_like(query)}%"
51
+ predicates = config[:search_by].map { |field| model.arel_table[field].matches(pattern) }
52
+ records.where(predicates.reduce(&:or))
53
+ end
54
+ end
@@ -1,44 +1,6 @@
1
1
  class TamSelectRemoteController < ApplicationController
2
- include TamSelectPaginatable
2
+ include TamSelectRemote
3
3
 
4
- class_attribute :tam_select_config, instance_writer: false
5
-
6
- def self.tam_select(model:, label:, value: :id, search_by: nil, per_page: 20)
7
- self.tam_select_config = {
8
- model: model,
9
- label: label,
10
- value: value,
11
- search_by: Array(search_by || label),
12
- per_page: per_page
13
- }.freeze
14
- end
15
-
16
- def index
17
- config = self.class.tam_select_config
18
- raise "Configure #{self.class.name} with tam_select(...)" unless config
19
-
20
- records = search(tam_select_scope(config), config)
21
- render json: tam_select_payload(
22
- records,
23
- label: config[:label],
24
- value: config[:value],
25
- per_page: config[:per_page]
26
- )
27
- end
28
-
29
- private
30
-
31
- def tam_select_scope(config)
32
- config[:model].all
33
- end
34
-
35
- def search(records, config)
36
- query = params[:q].to_s.strip
37
- return records if query.blank?
38
-
39
- model = config[:model]
40
- pattern = "%#{model.sanitize_sql_like(query)}%"
41
- predicates = config[:search_by].map { |field| model.arel_table[field].matches(pattern) }
42
- records.where(predicates.reduce(&:or))
43
- end
4
+ # Configure this base controller in a subclass, or include TamSelectRemote
5
+ # directly in an existing resource controller. See the Tam Select README.
44
6
  end
@@ -1,3 +1,3 @@
1
1
  module TamSelect
2
- VERSION = "0.1.1"
2
+ VERSION = "1.1.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tam_select
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tamiru Hailu
@@ -62,6 +62,7 @@ files:
62
62
  - lib/generators/tam_select/templates/tam_select_helper.rb
63
63
  - lib/generators/tam_select/templates/tam_select_input.rb
64
64
  - lib/generators/tam_select/templates/tam_select_paginatable.rb
65
+ - lib/generators/tam_select/templates/tam_select_remote.rb
65
66
  - lib/generators/tam_select/templates/tam_select_remote_controller.rb
66
67
  - lib/tam_select.rb
67
68
  - lib/tam_select/engine.rb