admino 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +393 -50
- data/lib/admino/query.rb +1 -1
- data/lib/admino/query/base.rb +15 -14
- data/lib/admino/query/configuration.rb +6 -6
- data/lib/admino/query/dsl.rb +3 -3
- data/lib/admino/query/filter_group_presenter.rb +1 -1
- data/lib/admino/query/{field.rb → search_field.rb} +1 -1
- data/lib/admino/query/sorting.rb +2 -1
- data/lib/admino/query/sorting_presenter.rb +11 -1
- data/lib/admino/table/head_row.rb +5 -4
- data/lib/admino/table/presenter.rb +18 -9
- data/lib/admino/table/resource_row.rb +2 -6
- data/lib/admino/version.rb +1 -1
- data/spec/admino/query/base_spec.rb +14 -14
- data/spec/admino/query/dsl_spec.rb +8 -8
- data/spec/admino/query/filter_group_presenter_spec.rb +1 -1
- data/spec/admino/query/filter_group_spec.rb +1 -1
- data/spec/admino/query/{field_spec.rb → search_field_spec.rb} +13 -13
- data/spec/admino/query/sorting_presenter_spec.rb +28 -1
- data/spec/admino/query/sorting_spec.rb +2 -2
- data/spec/admino/table/presenter_spec.rb +4 -9
- data/spec/spec_helper.rb +6 -8
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c15fcada38f4aa3e23bbc6b1a460565a632fb7aa
|
4
|
+
data.tar.gz: 2420006c1f8910d432cd5a3da44776ff8a9fc94b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3a34ac25cf486afeb7beb851dd6eecec93de4d7d6f47371570b0451cc533ed423c53a6a47ad5b6f9a80595533e5a0b6940a8b3da754f44e3793dc16fca330f9
|
7
|
+
data.tar.gz: 08c7f2fc10cb1762680895ce7cb18b257c20528a42de033d4e0c148ce8bf3360e07bc0c6d7ab08ae945af30cf3842eabf1a995f4707e3fcd3cfddfec9ac32940
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,7 @@ So yes, if you're starting a small, short-lived project, go ahead with them, it
|
|
19
19
|
|
20
20
|
### A modular approach to the problem
|
21
21
|
|
22
|
-
The great thing is that you don't need to write a lot of code to get a more maintainable and modular administrative area.
|
22
|
+
The great thing is that you don't need to write a lot of code to get a more maintainable and modular administrative area.
|
23
23
|
Gems like [Inherited Resources](https://github.com/josevalim/inherited_resources) and [Simple Form](https://github.com/plataformatec/simple_form), combined with [Rails 3.1+ template-inheritance](http://railscasts.com/episodes/269-template-inheritance) already give you ~90% of the time-saving features and the same super-DRY, declarative code that administrative interfaces offer, but with a far more relaxed contract.
|
24
24
|
|
25
25
|
If a particular controller or view needs something different from the standard CRUD/REST treatment, you can just avoid using those gems in that specific context, and fall back to standard Rails code. No workarounds, no facepalms. It seems easy, right? It is.
|
@@ -38,68 +38,163 @@ And then execute:
|
|
38
38
|
|
39
39
|
## Admino::Query::Base
|
40
40
|
|
41
|
-
|
41
|
+
`Admino::Query::Base` implements the [Query object](http://martinfowler.com/eaaCatalog/queryObject.html) pattern, that is, an object responsible for returning a result set (ie. an `ActiveRecord::Relation`) based on business rules.
|
42
42
|
|
43
|
-
Given a `Task` model
|
43
|
+
Given a `Task` model, we can generate a `TasksQuery` query object subclassing `Admino::Query::Base`:
|
44
44
|
|
45
45
|
```ruby
|
46
|
-
class
|
47
|
-
|
46
|
+
class TasksQuery < Admino::Query::Base
|
47
|
+
end
|
48
|
+
```
|
48
49
|
|
49
|
-
|
50
|
-
scope :pending, -> { where(completed: false) }
|
50
|
+
Each query object gets initialized with a hash of params, and features a `#scope` method that returns the filtered/sorted result set. As you may have guessed, query objects can be great companions to index controller actions:
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
```ruby
|
53
|
+
class TasksController < ApplicationController
|
54
|
+
def index
|
55
|
+
@query = TasksQuery.new(params)
|
56
|
+
@tasks = @query.scope
|
57
|
+
end
|
54
58
|
end
|
55
59
|
```
|
56
60
|
|
57
|
-
|
61
|
+
### Building the query itself
|
62
|
+
|
63
|
+
You can specify how a `TaskQuery` must build a result set through a simple DSL.
|
64
|
+
|
65
|
+
#### `starting_scope`
|
66
|
+
|
67
|
+
The `starting_scope` method is in charge of defining the scope that will start the filtering/ordering chain:
|
58
68
|
|
59
69
|
```ruby
|
60
70
|
class TasksQuery < Admino::Query::Base
|
61
|
-
starting_scope {
|
71
|
+
starting_scope { Task.all }
|
72
|
+
end
|
73
|
+
|
74
|
+
Task.create(title: 'Low priority task')
|
75
|
+
|
76
|
+
TaskQuery.new.scope.count # => 1
|
77
|
+
```
|
78
|
+
|
79
|
+
#### `search_field`
|
80
|
+
|
81
|
+
Once you define the following field:
|
62
82
|
|
63
|
-
|
83
|
+
```ruby
|
84
|
+
class TasksQuery < Admino::Query::Base
|
85
|
+
# ...
|
86
|
+
search_field :title_matches
|
87
|
+
end
|
88
|
+
```
|
89
|
+
The `#scope` method will check the presence of the `params[:query][:title_matches]` key. If it finds it, it will augment the query with a
|
90
|
+
named scope called `:title_matches`, expected to be found within the `Task` model, that needs to accept an argument.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class Task < ActiveRecord::Base
|
94
|
+
scope :title_matches, ->(text) {
|
95
|
+
where('title ILIKE ?', "%#{text}%")
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
Task.create(title: 'Low priority task')
|
100
|
+
Task.create(title: 'Fix me ASAP!!1!')
|
101
|
+
|
102
|
+
TaskQuery.new.scope.count # => 2
|
103
|
+
TaskQuery.new(query: { title_matches: 'ASAP' }).scope.count # => 1
|
104
|
+
```
|
105
|
+
|
106
|
+
#### `filter_by`
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class TasksQuery < Admino::Query::Base
|
110
|
+
# ...
|
64
111
|
filter_by :status, [:completed, :pending]
|
65
|
-
sorting :by_due_date, :by_title
|
66
112
|
end
|
67
113
|
```
|
68
114
|
|
69
|
-
|
115
|
+
Just like a search field, with a declared filter group the `#scope` method will check the presence of a `params[:query][:status]` key. If it finds it (and its value corresponds to one of the declared scopes) it will augment the query the scope itself:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
class Task < ActiveRecord::Base
|
119
|
+
scope :completed, -> { where(completed: true) }
|
120
|
+
scope :pending, -> { where(completed: false) }
|
121
|
+
end
|
122
|
+
|
123
|
+
Task.create(title: 'First task', completed: true)
|
124
|
+
Task.create(title: 'Second task', completed: true)
|
125
|
+
Task.create(title: 'Third task', completed: false)
|
126
|
+
|
127
|
+
TaskQuery.new.scope.count # => 3
|
128
|
+
TaskQuery.new(query: { status: 'completed' }).scope.count # => 2
|
129
|
+
TaskQuery.new(query: { status: 'pending' }).scope.count # => 1
|
130
|
+
TaskQuery.new(query: { status: 'foobar' }).scope.count # => 3
|
131
|
+
```
|
132
|
+
|
133
|
+
#### `sorting`
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class TasksQuery < Admino::Query::Base
|
137
|
+
# ...
|
138
|
+
sorting :by_due_date, :by_title
|
139
|
+
end
|
140
|
+
```
|
70
141
|
|
71
|
-
|
72
|
-
* a set of **search fields**, which represent model scopes that require an input to filter the result set;
|
73
|
-
* a set of **filtering groups**, each of which is composed by a set of scopes that take no argument;
|
74
|
-
* a set of **sorting scopes** that take a sigle argument (`:asc` or `:desc`) and thus are able to order the result set in both directions;
|
142
|
+
Once you declare some sorting scopes, the query object looks for a `params[:sorting]` key. If it exists (and corresponds to one of the declared scopes), it will augment the query with the scope itself. The model named scope will be called passing an argument that represents the direction of sorting (`:asc` or `:desc`).
|
75
143
|
|
76
|
-
|
144
|
+
The direction passed to the scope will depend on the value of `params[:sort_order]`, and will default to `:asc`:
|
77
145
|
|
78
146
|
```ruby
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
}
|
147
|
+
class Task < ActiveRecord::Base
|
148
|
+
scope :by_due_date, ->(direction) { order(due_date: direction) }
|
149
|
+
scope :by_title, ->(direction) { order(title: direction) }
|
150
|
+
end
|
151
|
+
|
152
|
+
expired_task = Task.create(due_date: 1.year.ago)
|
153
|
+
future_task = Task.create(due_date: 1.week.since)
|
87
154
|
|
88
|
-
|
155
|
+
TaskQuery.new(sorting: 'by_due_date', sort_order: 'desc').scope # => [ future_task, expired_task ]
|
156
|
+
TaskQuery.new(sorting: 'by_due_date', sort_order: 'asc').scope # => [ expired_task, future_task ]
|
157
|
+
TaskQuery.new(sorting: 'by_due_date').scope # => [ expired_task, future_task ]
|
89
158
|
```
|
90
159
|
|
91
|
-
|
160
|
+
#### `ending_scope`
|
161
|
+
|
162
|
+
It's very common ie. to paginate a result set. The block declared in the `ending_scope` block will be always appended to the end of the chain:
|
92
163
|
|
93
164
|
```ruby
|
94
|
-
class
|
95
|
-
|
96
|
-
@query = TasksQuery.new(params)
|
97
|
-
@project_tasks = @query.scope
|
98
|
-
end
|
165
|
+
class TasksQuery < Admino::Query::Base
|
166
|
+
ending_scope { |q| page(q.params[:page]) }
|
99
167
|
end
|
100
168
|
```
|
101
169
|
|
102
|
-
|
170
|
+
### Inspecting the query state
|
171
|
+
|
172
|
+
A query object supports various methods to inspect the available search fields, filters and sortings, and their state:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
query = TaskQuery.new
|
176
|
+
query.search_fields # => [ #<Admino::Query::SearchField>, ... ]
|
177
|
+
query.filter_groups # => [ #<Admino::Query::FilterGroup>, ... ]
|
178
|
+
|
179
|
+
search_field = query.search_field_by_name(:title_matches)
|
180
|
+
|
181
|
+
search_field.name # => :title_matches
|
182
|
+
search_field.present? # => true
|
183
|
+
search_field.value # => 'ASAP'
|
184
|
+
|
185
|
+
filter_group = query.filter_group_by_name(:status)
|
186
|
+
|
187
|
+
filter_group.name # => :status
|
188
|
+
filter_group.scopes # => [ :completed, :pending ]
|
189
|
+
filter_group.active_scope # => :completed
|
190
|
+
filter_group.is_scope_active?(:pending) # => false
|
191
|
+
|
192
|
+
sorting = query.sorting # => #<Admino::Query::Sorting>
|
193
|
+
sorting.scopes # => [ :by_title, :by_due_date ]
|
194
|
+
sorting.active_scope # => :by_due_date
|
195
|
+
sorting.is_scope_active?(:by_title) # => false
|
196
|
+
sorting.ascending? # => true
|
197
|
+
```
|
103
198
|
|
104
199
|
### Presenting search form and filters to the user
|
105
200
|
|
@@ -112,8 +207,8 @@ Admino also offers a [Showcase presenter](https://github.com/stefanoverna/showca
|
|
112
207
|
<%# generate the search form %>
|
113
208
|
<%= query.form do |q| %>
|
114
209
|
<p>
|
115
|
-
<%= q.label :
|
116
|
-
<%= q.text_field :
|
210
|
+
<%= q.label :title_matches %>
|
211
|
+
<%= q.text_field :title_matches %>
|
117
212
|
</p>
|
118
213
|
<p>
|
119
214
|
<%= q.submit %>
|
@@ -131,11 +226,25 @@ Admino also offers a [Showcase presenter](https://github.com/stefanoverna/showca
|
|
131
226
|
<% end %>
|
132
227
|
</ul>
|
133
228
|
<% end %>
|
229
|
+
|
230
|
+
<%# generate the sorting links %>
|
231
|
+
<h6>Sort by</h6>
|
232
|
+
<ul>
|
233
|
+
<% query.sorting.scopes.each do |scope| %>
|
234
|
+
<li>
|
235
|
+
<%= query.sorting.scope_link(scope) %>
|
236
|
+
</li>
|
237
|
+
<% end %>
|
238
|
+
</ul>
|
134
239
|
```
|
135
240
|
|
136
|
-
The great thing is that
|
241
|
+
The great thing is that:
|
137
242
|
|
138
|
-
|
243
|
+
* the search form gets automatically filled in with the last input the user submitted
|
244
|
+
* a `is-active` CSS class gets added to the currently active filter scopes
|
245
|
+
* if a particular filter link has been clicked and is now active, it is possible to deactivate it by clicking on the link again
|
246
|
+
* a `is-asc`/`is-desc` CSS class gets added to the currently active sorting scope
|
247
|
+
* if a particular sorting scope link has been clicked and is now in ascending order, it is possible to make it descending by clicking on the link again
|
139
248
|
|
140
249
|
### Simple Form support
|
141
250
|
|
@@ -150,7 +259,7 @@ en:
|
|
150
259
|
query:
|
151
260
|
attributes:
|
152
261
|
tasks_query:
|
153
|
-
|
262
|
+
title_matches: 'Title contains'
|
154
263
|
filter_groups:
|
155
264
|
tasks_query:
|
156
265
|
status:
|
@@ -158,13 +267,17 @@ en:
|
|
158
267
|
scopes:
|
159
268
|
completed: 'Completed'
|
160
269
|
pending: 'Pending'
|
270
|
+
sorting_scopes:
|
271
|
+
task_query:
|
272
|
+
by_due_date: 'By due date'
|
273
|
+
by_title: 'By title'
|
161
274
|
```
|
162
275
|
|
163
|
-
### Output
|
276
|
+
### Output customization
|
164
277
|
|
165
|
-
The
|
278
|
+
The presenter supports a number of optional arguments that allow a great amount of flexibility regarding customization of CSS classes, labels and HTML attributes. Please refer to the tests for the details.
|
166
279
|
|
167
|
-
|
280
|
+
### Overwriting the starting scope
|
168
281
|
|
169
282
|
Suppose you have to filter the tasks based on the `@current_user` work group. You can easily provide an alternative starting scope from the controller passing it as an argument to the `#scope` method:
|
170
283
|
|
@@ -175,9 +288,7 @@ def index
|
|
175
288
|
end
|
176
289
|
```
|
177
290
|
|
178
|
-
###
|
179
|
-
|
180
|
-
#### Coertions
|
291
|
+
### Coertions
|
181
292
|
|
182
293
|
Admino can perform automatic coertions from a param string input to the type needed by the model named scope:
|
183
294
|
|
@@ -204,17 +315,249 @@ If a specific coercion cannot be performed with the provided input, the scope wo
|
|
204
315
|
|
205
316
|
Please see the [`Coercible::Coercer::String`](https://github.com/solnic/coercible/blob/master/lib/coercible/coercer/string.rb) class for details.
|
206
317
|
|
207
|
-
###
|
318
|
+
### Default sorting
|
208
319
|
|
209
|
-
|
320
|
+
If you need to setup a default sorting, you can pass some optional arguments to a `scoping` declaration:
|
210
321
|
|
211
322
|
```ruby
|
212
323
|
class TasksQuery < Admino::Query::Base
|
213
|
-
|
324
|
+
# ...
|
325
|
+
sorting :by_due_date, :by_title,
|
326
|
+
default_scope: :by_due_date,
|
327
|
+
default_direction: :desc
|
214
328
|
end
|
215
329
|
```
|
216
330
|
|
217
331
|
## Admino::Table::Presenter
|
218
332
|
|
219
|
-
|
333
|
+
Admino offers a [Showcase collection presenter](https://github.com/stefanoverna/showcase) that makes it really easy to generate HTML tables from a set of records:
|
334
|
+
|
335
|
+
```erb
|
336
|
+
<%= Admino::Table::Presenter.new(@tasks, Task, self).to_html do |row, record| %>
|
337
|
+
<%= row.column :title %>
|
338
|
+
<%= row.column :completed do %>
|
339
|
+
<%= record.completed ? '✓' : '✗' %>
|
340
|
+
<% end %>
|
341
|
+
<%= row.column :due_date %>
|
342
|
+
<% end %>
|
343
|
+
```
|
344
|
+
|
345
|
+
```html
|
346
|
+
<table>
|
347
|
+
<thead>
|
348
|
+
<tr>
|
349
|
+
<th role='title'>Title</th>
|
350
|
+
<th role='completed'>Completed</th>
|
351
|
+
<th role='due_date'>Due date</th>
|
352
|
+
</tr>
|
353
|
+
<thead>
|
354
|
+
<tbody>
|
355
|
+
<tr id='task_1' class='is-even'>
|
356
|
+
<td role='title'>Call mum ASAP</td>
|
357
|
+
<td role='completed'>✓</td>
|
358
|
+
<td role='due_date'>2013-02-04</td>
|
359
|
+
</tr>
|
360
|
+
<tr id='task_2' class='is-odd'>
|
361
|
+
<!-- ... -->
|
362
|
+
</tr>
|
363
|
+
<tbody>
|
364
|
+
</table>
|
365
|
+
```
|
366
|
+
|
367
|
+
### Record actions
|
368
|
+
|
369
|
+
Often table rows needs to offer some kind of action associated with the record. The presenter implements the following DSL to support that:
|
370
|
+
|
371
|
+
```erb
|
372
|
+
<%= Admino::Table::Presenter.new(@tasks, Task, self).to_html do |row, record| %>
|
373
|
+
<%# ... %>
|
374
|
+
<%= row.actions do %>
|
375
|
+
<%= row.action :show, admin_task_path(record) %>
|
376
|
+
<%= row.action :edit, edit_admin_task_path(record) %>
|
377
|
+
<%= row.action :destroy, admin_task_path(record), method: :delete %>
|
378
|
+
<% end %>
|
379
|
+
<% end %>
|
380
|
+
```
|
381
|
+
|
382
|
+
```html
|
383
|
+
<table>
|
384
|
+
<thead>
|
385
|
+
<tr>
|
386
|
+
<!-- ... -->
|
387
|
+
<th role='actions'>Actions</th>
|
388
|
+
</tr>
|
389
|
+
<thead>
|
390
|
+
<tbody>
|
391
|
+
<tr id='task_1' class='is-even'>
|
392
|
+
<!-- ... -->
|
393
|
+
<td role='actions'>
|
394
|
+
<a href='/admin/tasks/1' role='show'>Show</a>
|
395
|
+
<a href='/admin/tasks/1/edit' role='edit'>Edit</a>
|
396
|
+
<a href='/admin/tasks/1' role='destroy' data-method='delete'>Destroy</a>
|
397
|
+
</td>
|
398
|
+
</tr>
|
399
|
+
<tbody>
|
400
|
+
</table>
|
401
|
+
```
|
402
|
+
|
403
|
+
### Sortable columns
|
404
|
+
|
405
|
+
Once a query object is passed to the presenter, columns can be associated to specific sorting scopes of the query object using the `sorting` option:
|
406
|
+
|
407
|
+
```erb
|
408
|
+
<% query = present(@query) %>
|
409
|
+
|
410
|
+
<%= Admino::Table::Presenter.new(@tasks, Task, query, self).to_html do |row, record| %>
|
411
|
+
<%= row.column :title, sorting: :by_title %>
|
412
|
+
<%= row.column :due_date, sorting: :by_due_date %>
|
413
|
+
<% end %>
|
414
|
+
```
|
415
|
+
|
416
|
+
This generates links that allow the visitor to sort the result set in ascending and descending direction:
|
417
|
+
|
418
|
+
```html
|
419
|
+
<table>
|
420
|
+
<thead>
|
421
|
+
<tr>
|
422
|
+
<th role='title'>
|
423
|
+
<a href="/admin/tasks?sorting=by_title&sort_order=desc" class='is-asc'>Title</a>
|
424
|
+
</th>
|
425
|
+
<th role='due_date'>
|
426
|
+
<a href="/admin/tasks?sorting=by_due_date&sort_order=asc" class='is-asc'>Due date</a>
|
427
|
+
</th>
|
428
|
+
</tr>
|
429
|
+
<thead>
|
430
|
+
<!-- ... -->
|
431
|
+
</table>
|
432
|
+
```
|
433
|
+
|
434
|
+
### Customizing the output
|
435
|
+
|
436
|
+
The `#column` and `#action` methods are very flexible, allowing youto change almost every aspect of the generated table cells:
|
437
|
+
|
438
|
+
```erb
|
439
|
+
<%= Admino::Table::Presenter.new(@tasks, Task, self).to_html(class: 'table-class') do |row, record| %>
|
440
|
+
<%= row.column :title, 'Custom title',
|
441
|
+
class: 'custom-class', role: 'custom-role', data: { custom: 'true' },
|
442
|
+
sorting: :by_title, sorting_html_options: { desc_class: 'down' }
|
443
|
+
%>
|
444
|
+
<%= row.action :show, admin_task_path(record), 'Custom label',
|
445
|
+
class: 'custom-class', role: 'custom-role', data: { custom: 'true' }
|
446
|
+
%>
|
447
|
+
<% end %>
|
448
|
+
```
|
449
|
+
|
450
|
+
If you need more power, you can also decide to subclass `Admino::Table::Presenter`. For each HTML element, there's a set of methods you can override to customize it's appeareance.
|
451
|
+
Table cells are generated through two collaborator classes: `Admino::Table::HeadRow` and `Admino::Table::ResourceRow`. You can easily replace them with a subclass if you want. To grasp the idea here's an example:
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
class CustomTablePresenter < Admino::Table::Presenter
|
455
|
+
private
|
456
|
+
|
457
|
+
def table_html_options
|
458
|
+
{ class: 'table-class' }
|
459
|
+
end
|
460
|
+
|
461
|
+
def tbody_tr_html_options(resource_index)
|
462
|
+
{ class: 'tr-class' }
|
463
|
+
end
|
464
|
+
|
465
|
+
def zebra_css_classes
|
466
|
+
%w(one two three)
|
467
|
+
end
|
468
|
+
|
469
|
+
def resource_row(resource, view_context)
|
470
|
+
ResourceRow.new(resource, view_context)
|
471
|
+
end
|
472
|
+
|
473
|
+
def head_row(collection_klass, query, view_context)
|
474
|
+
HeadRow.new(collection_klass, query, view_context)
|
475
|
+
end
|
476
|
+
|
477
|
+
class ResourceRow < Admino::Table::ResourceRow
|
478
|
+
private
|
479
|
+
|
480
|
+
def action_html_options(action_name)
|
481
|
+
{ class: 'action-class' }
|
482
|
+
end
|
483
|
+
|
484
|
+
def show_action_html_options
|
485
|
+
{ class: 'show-action-class' }
|
486
|
+
end
|
487
|
+
|
488
|
+
def column_html_options(attribute_name)
|
489
|
+
{ class: 'column-class' }
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
class HeadRow < Admino::Table::ResourceRow
|
494
|
+
def column_html_options(attribute_name)
|
495
|
+
{ class: 'column-class' }
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
```
|
500
|
+
|
501
|
+
Please refer to the tests for all the details.
|
502
|
+
|
503
|
+
### Inherited resources
|
504
|
+
|
505
|
+
If the action URLs can be programmatically generated, it becomes even easier to specify the table actions:
|
506
|
+
|
507
|
+
```erb
|
508
|
+
<%= CustomTablePresenter.new(@tasks, Task, self).to_html do |row, record| %>
|
509
|
+
<%# ... %>
|
510
|
+
<%= row.actions :show, :edit, :destroy %>
|
511
|
+
<% end %>
|
512
|
+
```
|
513
|
+
For instance, using [Inherited Resources](https://github.com/josevalim/inherited_resources) to generate controller actions, you can use its [helper methods](https://github.com/josevalim/inherited_resources#url-helpers) to build a custom subclass of `Admino::Table::Presenter`:
|
514
|
+
|
515
|
+
```ruby
|
516
|
+
class CustomTablePresenter < Admino::Table::Presenter
|
517
|
+
private
|
518
|
+
|
519
|
+
def resource_row(resource, view_context)
|
520
|
+
ResourceRow.new(resource, view_context)
|
521
|
+
end
|
522
|
+
|
523
|
+
class ResourceRow < Admino::Table::ResourceRow
|
524
|
+
def show_action_url
|
525
|
+
h.resource_url(resource)
|
526
|
+
end
|
527
|
+
|
528
|
+
def edit_action_url
|
529
|
+
h.edit_resource_url(resource)
|
530
|
+
end
|
531
|
+
|
532
|
+
def destroy_action_url
|
533
|
+
h.resource_url(resource)
|
534
|
+
end
|
535
|
+
|
536
|
+
def destroy_action_html_options
|
537
|
+
{ method: :delete }
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
```
|
542
|
+
|
543
|
+
### I18n
|
544
|
+
|
545
|
+
Column titles are generated using the model [`#human_attribute_name`](http://apidock.com/rails/ActiveRecord/Base/human_attribute_name/class) method, so if you already translated the model attribute names, you're good to go. To translate actions, please refer to the following YAML file:
|
546
|
+
|
547
|
+
```yaml
|
548
|
+
en:
|
549
|
+
activerecord:
|
550
|
+
attributes:
|
551
|
+
task:
|
552
|
+
title: 'Title'
|
553
|
+
due_date: 'Due date'
|
554
|
+
completed: 'Completed?'
|
555
|
+
table:
|
556
|
+
actions:
|
557
|
+
task:
|
558
|
+
title: 'Actions'
|
559
|
+
show: 'Details'
|
560
|
+
edit: 'Edit task'
|
561
|
+
destroy: 'Delete'
|
562
|
+
```
|
220
563
|
|
data/lib/admino/query.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'admino/query/base'
|
2
2
|
require 'admino/query/base_presenter'
|
3
3
|
require 'admino/query/configuration'
|
4
|
-
require 'admino/query/
|
4
|
+
require 'admino/query/search_field'
|
5
5
|
require 'admino/query/filter_group'
|
6
6
|
require 'admino/query/filter_group_presenter'
|
7
7
|
require 'admino/query/sorting'
|
data/lib/admino/query/base.rb
CHANGED
@@ -14,7 +14,7 @@ module Admino
|
|
14
14
|
|
15
15
|
attr_reader :params
|
16
16
|
attr_reader :filter_groups
|
17
|
-
attr_reader :
|
17
|
+
attr_reader :search_fields
|
18
18
|
attr_reader :sorting
|
19
19
|
|
20
20
|
def self.i18n_scope
|
@@ -26,7 +26,7 @@ module Admino
|
|
26
26
|
@config = config
|
27
27
|
|
28
28
|
init_filter_groups
|
29
|
-
|
29
|
+
init_search_fields
|
30
30
|
init_sorting
|
31
31
|
end
|
32
32
|
|
@@ -39,11 +39,11 @@ module Admino
|
|
39
39
|
|
40
40
|
scope_builder = starting_scope
|
41
41
|
|
42
|
-
scope_augmenters =
|
42
|
+
scope_augmenters = search_fields + filter_groups
|
43
43
|
scope_augmenters << sorting if sorting
|
44
44
|
|
45
|
-
scope_augmenters.each do |
|
46
|
-
scope_builder =
|
45
|
+
scope_augmenters.each do |search_field|
|
46
|
+
scope_builder = search_field.augment_scope(scope_builder)
|
47
47
|
end
|
48
48
|
|
49
49
|
if config.ending_scope_callable
|
@@ -69,12 +69,12 @@ module Admino
|
|
69
69
|
@filter_groups[name]
|
70
70
|
end
|
71
71
|
|
72
|
-
def
|
73
|
-
@
|
72
|
+
def search_fields
|
73
|
+
@search_fields.values
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
@
|
76
|
+
def search_field_by_name(name)
|
77
|
+
@search_fields[name]
|
78
78
|
end
|
79
79
|
|
80
80
|
private
|
@@ -87,16 +87,17 @@ module Admino
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
-
def
|
91
|
-
@
|
92
|
-
config.
|
93
|
-
@
|
90
|
+
def init_search_fields
|
91
|
+
@search_fields = {}
|
92
|
+
config.search_fields.each do |config|
|
93
|
+
@search_fields[config.name] = SearchField.new(config, params)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
97
|
def init_sorting
|
98
98
|
if config.sorting
|
99
|
-
|
99
|
+
i18n_key = self.class.model_name.i18n_key
|
100
|
+
@sorting = Sorting.new(config.sorting, params, i18n_key)
|
100
101
|
end
|
101
102
|
end
|
102
103
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Admino
|
2
2
|
module Query
|
3
3
|
class Configuration
|
4
|
-
class
|
4
|
+
class SearchField
|
5
5
|
attr_reader :name
|
6
6
|
attr_reader :coerce_to
|
7
7
|
|
@@ -47,20 +47,20 @@ module Admino
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
attr_reader :
|
50
|
+
attr_reader :search_fields
|
51
51
|
attr_reader :filter_groups
|
52
52
|
attr_reader :sorting
|
53
53
|
attr_accessor :starting_scope_callable
|
54
54
|
attr_accessor :ending_scope_callable
|
55
55
|
|
56
56
|
def initialize
|
57
|
-
@
|
57
|
+
@search_fields = []
|
58
58
|
@filter_groups = []
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
62
|
-
|
63
|
-
self.
|
61
|
+
def add_search_field(name, options = {})
|
62
|
+
SearchField.new(name, options).tap do |search_field|
|
63
|
+
self.search_fields << search_field
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
data/lib/admino/query/dsl.rb
CHANGED
@@ -5,11 +5,11 @@ module Admino
|
|
5
5
|
@config ||= Admino::Query::Configuration.new
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
config.
|
8
|
+
def search_field(name, options = {})
|
9
|
+
config.add_search_field(name, options)
|
10
10
|
|
11
11
|
define_method name do
|
12
|
-
|
12
|
+
search_field_by_name(name).value
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
data/lib/admino/query/sorting.rb
CHANGED
@@ -7,8 +7,9 @@ module Admino
|
|
7
7
|
class Sorting
|
8
8
|
attr_reader :params
|
9
9
|
attr_reader :config
|
10
|
+
attr_reader :query_i18n_key
|
10
11
|
|
11
|
-
def initialize(config, params)
|
12
|
+
def initialize(config, params, query_i18n_key = nil)
|
12
13
|
@config = config
|
13
14
|
@params = ActiveSupport::HashWithIndifferentAccess.new(params)
|
14
15
|
end
|
@@ -3,9 +3,11 @@ require 'showcase'
|
|
3
3
|
module Admino
|
4
4
|
module Query
|
5
5
|
class SortingPresenter < Showcase::Presenter
|
6
|
-
def scope_link(scope,
|
6
|
+
def scope_link(scope, *args)
|
7
7
|
options = args.extract_options!
|
8
8
|
|
9
|
+
label = args.first || scope_name(scope)
|
10
|
+
|
9
11
|
desc_class = options.delete(:desc_class) { 'is-desc' }
|
10
12
|
asc_class = options.delete(:asc_class) { 'is-asc' }
|
11
13
|
|
@@ -35,6 +37,14 @@ module Admino
|
|
35
37
|
|
36
38
|
params
|
37
39
|
end
|
40
|
+
|
41
|
+
def scope_name(scope)
|
42
|
+
I18n.t(
|
43
|
+
:"#{query_i18n_key}.#{scope}",
|
44
|
+
scope: 'query.sorting_scopes',
|
45
|
+
default: scope.to_s.titleize.capitalize
|
46
|
+
)
|
47
|
+
end
|
38
48
|
end
|
39
49
|
end
|
40
50
|
end
|
@@ -20,7 +20,10 @@ module Admino
|
|
20
20
|
label = I18n.t(
|
21
21
|
:"#{resource_klass.model_name.i18n_key}.title",
|
22
22
|
scope: 'table.actions',
|
23
|
-
default: [
|
23
|
+
default: [
|
24
|
+
:title,
|
25
|
+
'Actions'
|
26
|
+
]
|
24
27
|
)
|
25
28
|
|
26
29
|
@columns << h.content_tag(:th, label.to_s, default_options)
|
@@ -59,9 +62,7 @@ module Admino
|
|
59
62
|
private
|
60
63
|
|
61
64
|
def column_html_options(attribute_name)
|
62
|
-
|
63
|
-
{ role: attribute_name.to_s.gsub(/_/, '-') }
|
64
|
-
end
|
65
|
+
{ role: attribute_name.to_s.gsub(/_/, '-') }
|
65
66
|
end
|
66
67
|
end
|
67
68
|
end
|
@@ -9,12 +9,12 @@ module Admino
|
|
9
9
|
attr_reader :query
|
10
10
|
|
11
11
|
def self.tag_helper(name, tag, options = {})
|
12
|
-
|
12
|
+
options_method = :"#{name}_html_options"
|
13
13
|
|
14
14
|
define_method :"#{name}_tag" do |*args, &block|
|
15
15
|
options = args.extract_options!
|
16
|
-
if respond_to?(
|
17
|
-
default_options = send(
|
16
|
+
if respond_to?(options_method, true)
|
17
|
+
default_options = send(options_method, *args)
|
18
18
|
html_options = Showcase::Helpers::HtmlOptions.new(default_options)
|
19
19
|
html_options.merge_attrs!(options)
|
20
20
|
options = html_options.to_h
|
@@ -50,11 +50,8 @@ module Admino
|
|
50
50
|
end <<
|
51
51
|
tbody_tag do
|
52
52
|
collection.each_with_index.map do |resource, index|
|
53
|
-
|
54
|
-
|
55
|
-
id: resource.dom_id
|
56
|
-
}
|
57
|
-
tbody_tr_tag(resource, index, tr_html_options) do
|
53
|
+
html_options = base_tbody_tr_html_options(resource, index)
|
54
|
+
tbody_tr_tag(resource, index, html_options) do
|
58
55
|
row = resource_row(resource, view_context)
|
59
56
|
h.capture(row, resource, &block) if block_given?
|
60
57
|
row.to_html
|
@@ -67,7 +64,7 @@ module Admino
|
|
67
64
|
private
|
68
65
|
|
69
66
|
def collection
|
70
|
-
|
67
|
+
object
|
71
68
|
end
|
72
69
|
|
73
70
|
def head_row(collection_klass, query, view_context)
|
@@ -78,6 +75,18 @@ module Admino
|
|
78
75
|
ResourceRow.new(resource, view_context)
|
79
76
|
end
|
80
77
|
|
78
|
+
def base_tbody_tr_html_options(resource, index)
|
79
|
+
options = {
|
80
|
+
class: zebra_css_classes[index % zebra_css_classes.size]
|
81
|
+
}
|
82
|
+
|
83
|
+
if resource.respond_to?(:dom_id)
|
84
|
+
options[:id] = resource.dom_id
|
85
|
+
end
|
86
|
+
|
87
|
+
options
|
88
|
+
end
|
89
|
+
|
81
90
|
def zebra_css_classes
|
82
91
|
%w(is-even is-odd)
|
83
92
|
end
|
@@ -119,15 +119,11 @@ module Admino
|
|
119
119
|
private
|
120
120
|
|
121
121
|
def action_html_options(action_name)
|
122
|
-
|
123
|
-
{ role: action_name.to_s.gsub(/_/, '-') }
|
124
|
-
end
|
122
|
+
{ role: action_name.to_s.gsub(/_/, '-') }
|
125
123
|
end
|
126
124
|
|
127
125
|
def column_html_options(attribute_name)
|
128
|
-
|
129
|
-
{ role: attribute_name.to_s.gsub(/_/, '-') }
|
130
|
-
end
|
126
|
+
{ role: attribute_name.to_s.gsub(/_/, '-') }
|
131
127
|
end
|
132
128
|
end
|
133
129
|
end
|
data/lib/admino/version.rb
CHANGED
@@ -19,18 +19,18 @@ module Admino
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
context 'with a declared
|
22
|
+
context 'with a declared search_field' do
|
23
23
|
let(:config) { Configuration.new }
|
24
|
-
let(:
|
24
|
+
let(:search_field_config) { config.add_search_field(:search_field) }
|
25
25
|
|
26
26
|
before do
|
27
|
-
|
27
|
+
search_field_config
|
28
28
|
end
|
29
29
|
|
30
|
-
it 'returns a configured
|
31
|
-
|
32
|
-
expect(
|
33
|
-
expect(
|
30
|
+
it 'returns a configured SearchField' do
|
31
|
+
search_field = query.search_field_by_name(:search_field)
|
32
|
+
expect(search_field.config).to eq search_field_config
|
33
|
+
expect(search_field.params).to eq params
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -107,25 +107,25 @@ module Admino
|
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
110
|
-
context 'with a set of
|
111
|
-
let(:
|
110
|
+
context 'with a set of search_fields and filter_groups' do
|
111
|
+
let(:search_field_config) { config.add_search_field(:search_field) }
|
112
112
|
let(:filter_group_config) { config.add_filter_group(:filter_group, [:one, :two]) }
|
113
|
-
let(:
|
113
|
+
let(:scope_chained_with_search_field) { double('scope 1') }
|
114
114
|
let(:final_chain) { double('scope 2') }
|
115
115
|
|
116
116
|
before do
|
117
|
-
|
117
|
+
search_field_config
|
118
118
|
filter_group_config
|
119
119
|
query
|
120
120
|
|
121
|
-
query.
|
121
|
+
query.search_field_by_name(:search_field).
|
122
122
|
stub(:augment_scope).
|
123
123
|
with(starting_scope).
|
124
|
-
and_return(
|
124
|
+
and_return(scope_chained_with_search_field)
|
125
125
|
|
126
126
|
query.filter_group_by_name(:filter_group).
|
127
127
|
stub(:augment_scope).
|
128
|
-
with(
|
128
|
+
with(scope_chained_with_search_field).
|
129
129
|
and_return(final_chain)
|
130
130
|
end
|
131
131
|
|
@@ -6,10 +6,10 @@ module Admino
|
|
6
6
|
let(:config) { TestQuery.config }
|
7
7
|
let(:instance) { TestQuery.new }
|
8
8
|
|
9
|
-
it 'allows #
|
10
|
-
|
11
|
-
expect(
|
12
|
-
expect(
|
9
|
+
it 'allows #search_field declaration' do
|
10
|
+
search_field = config.search_fields.last
|
11
|
+
expect(search_field.name).to eq :starting_from
|
12
|
+
expect(search_field.coerce_to).to eq :to_date
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'allows #filter_by declaration' do
|
@@ -33,13 +33,13 @@ module Admino
|
|
33
33
|
expect(config.ending_scope_callable.call).to eq 'end'
|
34
34
|
end
|
35
35
|
|
36
|
-
context 'with a
|
37
|
-
let(:
|
36
|
+
context 'with a search_field' do
|
37
|
+
let(:search_field) { double('SearchField', value: 'value') }
|
38
38
|
|
39
39
|
before do
|
40
|
-
instance.stub(:
|
40
|
+
instance.stub(:search_field_by_name).
|
41
41
|
with(:foo).
|
42
|
-
and_return(
|
42
|
+
and_return(search_field)
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'it generates a getter' do
|
@@ -123,7 +123,7 @@ module Admino
|
|
123
123
|
|
124
124
|
context 'if no translation is available' do
|
125
125
|
it 'falls back to a titleized version of the filter_group name' do
|
126
|
-
expect(presenter.name).to eq 'Filter
|
126
|
+
expect(presenter.name).to eq 'Filter group'
|
127
127
|
end
|
128
128
|
end
|
129
129
|
end
|
@@ -37,7 +37,7 @@ module Admino
|
|
37
37
|
let(:result) { filter_group.augment_scope(scope) }
|
38
38
|
let(:scope) { ScopeMock.new('original') }
|
39
39
|
|
40
|
-
context 'if the
|
40
|
+
context 'if the search_field has a value' do
|
41
41
|
let(:params) { { 'foo' => 'bar' } }
|
42
42
|
|
43
43
|
it 'returns the original scope chained with the filter_group scope' do
|
@@ -2,36 +2,36 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Admino
|
4
4
|
module Query
|
5
|
-
describe
|
6
|
-
subject(:
|
7
|
-
let(:config) { Configuration::
|
5
|
+
describe SearchField do
|
6
|
+
subject(:search_field) { SearchField.new(config, params) }
|
7
|
+
let(:config) { Configuration::SearchField.new(:foo) }
|
8
8
|
let(:params) { {} }
|
9
9
|
|
10
10
|
describe '#value' do
|
11
11
|
context 'with a value' do
|
12
12
|
let(:params) { { 'query' => { 'foo' => 'bar' } } }
|
13
13
|
|
14
|
-
it 'returns the param value for the
|
15
|
-
expect(
|
14
|
+
it 'returns the param value for the search_field' do
|
15
|
+
expect(search_field.value).to eq 'bar'
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
context 'else' do
|
20
20
|
it 'returns nil' do
|
21
|
-
expect(
|
21
|
+
expect(search_field.value).to be_nil
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
context 'with coertion' do
|
26
26
|
let(:config) {
|
27
|
-
Configuration::
|
27
|
+
Configuration::SearchField.new(:foo, coerce: :to_date)
|
28
28
|
}
|
29
29
|
|
30
30
|
context 'with a possible coertion' do
|
31
31
|
let(:params) { { 'query' => { 'foo' => '2014-10-05' } } }
|
32
32
|
|
33
|
-
it 'returns the coerced param value for the
|
34
|
-
expect(
|
33
|
+
it 'returns the coerced param value for the search_field' do
|
34
|
+
expect(search_field.value).to be_a Date
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -39,20 +39,20 @@ module Admino
|
|
39
39
|
let(:params) { { 'query' => { 'foo' => '' } } }
|
40
40
|
|
41
41
|
it 'returns nil' do
|
42
|
-
expect(
|
42
|
+
expect(search_field.value).to be_nil
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
describe '#augment_scope' do
|
49
|
-
let(:result) {
|
49
|
+
let(:result) { search_field.augment_scope(scope) }
|
50
50
|
let(:scope) { ScopeMock.new('original') }
|
51
51
|
|
52
|
-
context 'if the
|
52
|
+
context 'if the search_field has a value' do
|
53
53
|
let(:params) { { 'query' => { 'foo' => 'bar' } } }
|
54
54
|
|
55
|
-
it 'returns the original scope chained with the
|
55
|
+
it 'returns the original scope chained with the search_field scope' do
|
56
56
|
expect(result.chain).to eq [:foo, ['bar']]
|
57
57
|
end
|
58
58
|
end
|
@@ -5,7 +5,13 @@ module Admino
|
|
5
5
|
describe SortingPresenter do
|
6
6
|
subject(:presenter) { SortingPresenter.new(sorting, view) }
|
7
7
|
let(:view) { RailsViewContext.new }
|
8
|
-
let(:sorting)
|
8
|
+
let(:sorting) do
|
9
|
+
double(
|
10
|
+
'Sorting',
|
11
|
+
default_scope: 'by_name',
|
12
|
+
query_i18n_key: 'query_name'
|
13
|
+
)
|
14
|
+
end
|
9
15
|
let(:request_object) do
|
10
16
|
double(
|
11
17
|
'ActionDispatch::Request',
|
@@ -161,6 +167,27 @@ module Admino
|
|
161
167
|
end
|
162
168
|
end
|
163
169
|
end
|
170
|
+
|
171
|
+
describe '#scope_name' do
|
172
|
+
context do
|
173
|
+
before do
|
174
|
+
I18n.backend.store_translations(
|
175
|
+
:en,
|
176
|
+
query: { sorting_scopes: { query_name: { by_name: 'Sort by name' } } }
|
177
|
+
)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'returns a I18n translatable name for the scope' do
|
181
|
+
expect(presenter.scope_name(:by_name)).to eq 'Sort by name'
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'if no translation is available' do
|
186
|
+
it 'falls back to a titleized version of the scope name' do
|
187
|
+
expect(presenter.scope_name(:by_name)).to eq 'By name'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
164
191
|
end
|
165
192
|
end
|
166
193
|
end
|
@@ -73,7 +73,7 @@ module Admino
|
|
73
73
|
context 'with "desc" value' do
|
74
74
|
let(:params) { { 'sort_order' => 'desc' } }
|
75
75
|
|
76
|
-
it 'returns the param value for the
|
76
|
+
it 'returns the param value for the search_field' do
|
77
77
|
expect(sorting).not_to be_ascending
|
78
78
|
end
|
79
79
|
end
|
@@ -92,7 +92,7 @@ module Admino
|
|
92
92
|
let(:result) { sorting.augment_scope(scope) }
|
93
93
|
let(:scope) { ScopeMock.new('original') }
|
94
94
|
|
95
|
-
context 'if the
|
95
|
+
context 'if the search_field has a value' do
|
96
96
|
let(:params) { { 'sorting' => 'by_title', 'sort_order' => 'desc' } }
|
97
97
|
|
98
98
|
it 'returns the original scope chained with the current scope' do
|
@@ -11,20 +11,15 @@ module Admino
|
|
11
11
|
|
12
12
|
let(:collection) { [ first_post, second_post ] }
|
13
13
|
let(:first_post) { Post.new('1') }
|
14
|
-
let(:first_post_presenter) { double('PresentedPost', dom_id: 'post_1') }
|
15
14
|
let(:second_post) { Post.new('2') }
|
16
|
-
let(:second_post_presenter) { double('PresentedPost', dom_id: 'post_2') }
|
17
15
|
|
18
16
|
let(:head_row) { double('HeadRow', to_html: '<td id="thead_td"></td>'.html_safe) }
|
19
17
|
let(:resource_row) { double('ResourceRow', to_html: '<td id="tbody_td"></td>'.html_safe) }
|
20
18
|
|
21
19
|
before do
|
22
|
-
PostPresenter.stub(:new).with(first_post, view).and_return(first_post_presenter)
|
23
|
-
PostPresenter.stub(:new).with(second_post, view).and_return(second_post_presenter)
|
24
|
-
|
25
20
|
HeadRow.stub(:new).with(Post, query, view).and_return(head_row)
|
26
|
-
ResourceRow.stub(:new).with(
|
27
|
-
ResourceRow.stub(:new).with(
|
21
|
+
ResourceRow.stub(:new).with(first_post, view).and_return(resource_row)
|
22
|
+
ResourceRow.stub(:new).with(second_post, view).and_return(resource_row)
|
28
23
|
end
|
29
24
|
|
30
25
|
describe '#.to_html' do
|
@@ -78,8 +73,8 @@ module Admino
|
|
78
73
|
end
|
79
74
|
|
80
75
|
it 'calls it once for each collection member passing the ResourceRow instance and the member itself' do
|
81
|
-
expect(block_call_args[1]).to eq [resource_row,
|
82
|
-
expect(block_call_args[2]).to eq [resource_row,
|
76
|
+
expect(block_call_args[1]).to eq [resource_row, first_post]
|
77
|
+
expect(block_call_args[2]).to eq [resource_row, second_post]
|
83
78
|
end
|
84
79
|
end
|
85
80
|
|
data/spec/spec_helper.rb
CHANGED
@@ -32,8 +32,8 @@ class ScopeMock
|
|
32
32
|
end
|
33
33
|
|
34
34
|
class TestQuery < Admino::Query::Base
|
35
|
-
|
36
|
-
|
35
|
+
search_field :foo
|
36
|
+
search_field :starting_from, coerce: :to_date
|
37
37
|
|
38
38
|
filter_by :bar, [:one, :two]
|
39
39
|
|
@@ -45,7 +45,7 @@ class TestQuery < Admino::Query::Base
|
|
45
45
|
ending_scope { 'end' }
|
46
46
|
end
|
47
47
|
|
48
|
-
class Post < Struct.new(:key)
|
48
|
+
class Post < Struct.new(:key, :dom_id)
|
49
49
|
extend ActiveModel::Naming
|
50
50
|
extend ActiveModel::Translation
|
51
51
|
|
@@ -64,12 +64,10 @@ class Post < Struct.new(:key)
|
|
64
64
|
def to_key
|
65
65
|
[key]
|
66
66
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
require 'showcase/traits'
|
70
67
|
|
71
|
-
|
72
|
-
|
68
|
+
def dom_id
|
69
|
+
"post_#{key}"
|
70
|
+
end
|
73
71
|
end
|
74
72
|
|
75
73
|
require 'action_view'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: admino
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefano Verna
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: showcase
|
@@ -187,9 +187,9 @@ files:
|
|
187
187
|
- lib/admino/query/base_presenter.rb
|
188
188
|
- lib/admino/query/configuration.rb
|
189
189
|
- lib/admino/query/dsl.rb
|
190
|
-
- lib/admino/query/field.rb
|
191
190
|
- lib/admino/query/filter_group.rb
|
192
191
|
- lib/admino/query/filter_group_presenter.rb
|
192
|
+
- lib/admino/query/search_field.rb
|
193
193
|
- lib/admino/query/sorting.rb
|
194
194
|
- lib/admino/query/sorting_presenter.rb
|
195
195
|
- lib/admino/table.rb
|
@@ -201,9 +201,9 @@ files:
|
|
201
201
|
- spec/admino/query/base_presenter_spec.rb
|
202
202
|
- spec/admino/query/base_spec.rb
|
203
203
|
- spec/admino/query/dsl_spec.rb
|
204
|
-
- spec/admino/query/field_spec.rb
|
205
204
|
- spec/admino/query/filter_group_presenter_spec.rb
|
206
205
|
- spec/admino/query/filter_group_spec.rb
|
206
|
+
- spec/admino/query/search_field_spec.rb
|
207
207
|
- spec/admino/query/sorting_presenter_spec.rb
|
208
208
|
- spec/admino/query/sorting_spec.rb
|
209
209
|
- spec/admino/table/head_row_spec.rb
|
@@ -239,9 +239,9 @@ test_files:
|
|
239
239
|
- spec/admino/query/base_presenter_spec.rb
|
240
240
|
- spec/admino/query/base_spec.rb
|
241
241
|
- spec/admino/query/dsl_spec.rb
|
242
|
-
- spec/admino/query/field_spec.rb
|
243
242
|
- spec/admino/query/filter_group_presenter_spec.rb
|
244
243
|
- spec/admino/query/filter_group_spec.rb
|
244
|
+
- spec/admino/query/search_field_spec.rb
|
245
245
|
- spec/admino/query/sorting_presenter_spec.rb
|
246
246
|
- spec/admino/query/sorting_spec.rb
|
247
247
|
- spec/admino/table/head_row_spec.rb
|