Skip to content

Commit

Permalink
Add Column#value_for to extract values
Browse files Browse the repository at this point in the history
This helps us customize how to get different values for each column in a
table without having to define special renderers.
  • Loading branch information
foca committed Mar 22, 2024
1 parent 9151dcb commit d2884f2
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 1 deletion.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ And then, on `app/views/products/_product.html.erb`:
``` erb
<tr>
<% columns.each do |column| %>
<td><%= product.public_send(column.name) %></td>
<td><%= column.value_for(product) %></td>
<% end %>
</tr>
```
Expand Down Expand Up @@ -309,6 +309,36 @@ separately:
</tr>
```

#### Extracting values for a column/row pair

You can use the `Column#value_for(row)` method to extract the value for a column
given a row. For example, in your column renderer, you might do

``` erb
<td>
<%= column.value_for(row) %>
</td>
```

The behavior of `#value_for` is controlled by the Column's `accessor` option. By
default, it will try to `public_send` the column's `name` to the row object.

``` ruby
class ProductsQuery < ApplicationQuery
# In this case, `column.value_for(row)` is equivalent to `row.name`
column :name

# Here, we're assuming the model defines a `#price` method (e.g. via
# `composed_of` that returns something that's better to display than
# the number of cents)
column :price_in_cents, accessor: :price

# By using a Proc as the accessor, we can do more advanced formatting of the
# value, without having to do anything special when rendering the cell.
column :purchases_count, accessor: ->(row) { "purchase".pluralize(row.purchases_count) }
end
```

### Internationalization
[i18n]: #internationalization

Expand Down
17 changes: 17 additions & 0 deletions lib/dtb/column.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class Column < QueryBuilder
# at the end of each row, or a checkbox at the start.
option :database, default: true, required: true

# @!attribute [rw] accessor
# The accessor to use to extract the value for this column for a given
# row. Accepts a Symbol, String, or Proc. If given a Symbol or String, it
# will attempt to call that method on the row object. If given a Proc, it
# will call the Proc with the row object as its argument.
option :accessor, default: ->(row) { row.public_send(name) if database? }

# @!endgroup

# Looks up the column's header in the i18n sources. If the column is
Expand All @@ -73,6 +80,16 @@ def header
i18n_lookup(:columns, default: "")
end

# Extracts the value from the row using what you've configured as an
# {#accessor}.
def value_for(row)
if options[:accessor].respond_to?(:to_proc)
instance_exec(row, &options[:accessor].to_proc)
elsif options[:accessor].respond_to?(:to_sym) && row.respond_to?(options[:accessor].to_sym)
row.public_send(options[:accessor].to_sym)
end
end

# @return [Boolean] whether this column actually affects the query or not.
# @see #database
def database?
Expand Down
19 changes: 19 additions & 0 deletions test/column_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ def test_can_resolve_headers_hierarchically
assert_equal "Base Bar", bar.header
end

def test_accesor
row = Struct.new(:value).new(42)

as_proc = DTB::Column.new(:value, accessor: ->(row) { row.value })
assert_equal 42, as_proc.value_for(row)

as_symbol = DTB::Column.new(:value, accessor: :value)
assert_equal 42, as_symbol.value_for(row)

as_string = DTB::Column.new(:value, accessor: "value")
assert_equal 42, as_string.value_for(row)

default = DTB::Column.new(:value)
assert_equal 42, default.value_for(row)

default_when_not_database = DTB::Column.new(:value, database: false)
assert_nil default_when_not_database.value_for(row)
end

def test_rendering
column = DTB::Column.new(:value) { |scope| scope }
assert_nil column.renderer
Expand Down

0 comments on commit d2884f2

Please sign in to comment.