Conversation
53f50e0 to
92f01f3
Compare
|
Thank you for this. I hope I can remember things clearly, let's see what we can do here... 👀
|
92f01f3 to
f3f53c2
Compare
f3f53c2 to
f377679
Compare
|
@pablobm |
This is for compatibility reasons. module Administrate
module Field
class Base
def initialize(attribute, _data, page, options = {})
@attribute = attribute
@page = page
@resource = options.delete(:resource)
@options = options
@data = read_value
endShould I fix all the RSpec tests? |
I believe this case should not be an issue. For custom fields, you can override read_value to fetch data and format it using any method you prefer. |
pablobm
left a comment
There was a problem hiding this comment.
OK, I'm more onboard now. Thank you for the changes and explanation 🙂
Regarding the data argument, for some time I have been thinking that it should disappear, but let's not do that now. This PR can be an intermediate step with back-compatibility, towards a future where fields calculate their value instead of being calculated at the Page level.
I wasn't sure about the :getter, but after playing with it a bit I'm liking it better. I think it can stay.
| else | ||
| if data.nil? | ||
| resource&.public_send(attribute) | ||
| else | ||
| data | ||
| end | ||
| end |
There was a problem hiding this comment.
Unfortunately, the example I mentioned with Foobar doesn't work: public_send fails because the method doesn't exist in the resource. Note that the safe navigation operator (&.) doesn't help here because it's useful only when the resource is nil.
However, I think it can be fixed with a respond_to? instead, as below:
| else | |
| if data.nil? | |
| resource&.public_send(attribute) | |
| else | |
| data | |
| end | |
| end | |
| elsif data.nil? | |
| resource.respond_to?(attribute) ? resource.public_send(attribute) : nil | |
| else | |
| data | |
| end | |
| end |
This should also fix the linter error asking for elsif to be used.
There was a problem hiding this comment.
Apologies for the oversight. I’ve made the fixes and checked with standardrb.
I reviewed the your reference code and felt that try would be sufficient, so I rewrote it using try. Do you have any concerns about this approach?
| nickname: Field::String.with_options( | ||
| getter: ->(field) { "Mr. #{field.resource.name}man" if field.resource.name.present? }, | ||
| searchable: false | ||
| ), |
There was a problem hiding this comment.
Thinking of an example that doesn't feel too artificial: how about a full_address field for Order? Something like this:
full_address: Field::String.with_options(
getter: ->(field) {
r = field.resource
[
r.address_line_one,
r.address_line_two,
r.address_city,
r.address_state,
r.address_zip
].compact.join("\n")
},
searchable: false
),Then its SHOW_PAGE_ATTRIBUTES can be something like this:
SHOW_PAGE_ATTRIBUTES = FORM_ATTRIBUTES
.except("address")
.merge(
"" => %i[customer full_address created_at updated_at],
"details" => %i[line_items total_price shipped_at payments]
)
.freezeThere was a problem hiding this comment.
Looks like searchable: false is excess. If getter has been provided it makes field as unsearchable.
There was a problem hiding this comment.
Have you thought about using decorators(like draper) for virtual fields?
There was a problem hiding this comment.
Sorry, should have been more clear: remove this example (which I find too artificial) and we can leave the one for the orders, which I think is better 🙂
There was a problem hiding this comment.
Good point on searchable. Perhaps it should be false by default if getter is supplied? Otherwise it breaks the search.
There was a problem hiding this comment.
@Nitr - It's a bit tricky due to how Administrate works internally. Also we tend to avoid adding dependencies like draper.
|
|
||
| module Administrate | ||
| module Field | ||
| class VirtualField < Field::Base |
There was a problem hiding this comment.
I wonder if we can find an example in the dashboard where we can define this type of virtual field without having to drop it here?
There was a problem hiding this comment.
What else is needed here? Documentation, maybe?
Or would an approach using an option like virtual_field: true to explicitly indicate virtual fields work better?
There was a problem hiding this comment.
Answering on the main thread 🙂
a83e2ff to
a575bde
Compare
pablobm
left a comment
There was a problem hiding this comment.
Coming along nicely! Let's see...
Could you please add documentation about the new :getter option?
Then about the spec/lib/fields/virtual_field_spec.rb spec. I think best to remove it completely and a good example like the following:
+require "administrate/field/receipt_link"
require "administrate/base_dashboard"
class PaymentDashboard < Administrate::BaseDashboard
ATTRIBUTE_TYPES = {
id: Field::Number,
+ receipt: Field::ReceiptLink,
created_at: Field::DateTime,
updated_at: Field::DateTime,
order: Field::BelongsTo
}
COLLECTION_ATTRIBUTES = [
- :id
+ :id,
+ :receipt
]
SHOW_PAGE_ATTRIBUTES = ATTRIBUTE_TYPES.keys
diff --git a/spec/example_app/app/views/fields/receipt_link/_index.html.erb b/spec/example_app/app/views/fields/receipt_link/_index.html.erb
new file mode 100644
index 00000000..9f755667
--- /dev/null
+++ b/spec/example_app/app/views/fields/receipt_link/_index.html.erb
@@ -0,0 +1 @@
+<%= link_to field.filename, field.data %>
diff --git a/spec/example_app/lib/administrate/field/receipt_link.rb b/spec/example_app/lib/administrate/field/receipt_link.rb
new file mode 100644
index 00000000..0756b259
--- /dev/null
+++ b/spec/example_app/lib/administrate/field/receipt_link.rb
@@ -0,0 +1,15 @@
+require "administrate/field/base"
+
+module Administrate
+ module Field
+ class ReceiptLink < Base
+ def data
+ "/files/receipts/#{filename}"
+ end
+
+ def filename
+ "receipt-#{resource.id}.pdf"
+ end
+ end
+ end
+endStill needs a _show template and a simple spec (doesn't need to be big). Could you please add that?
| nickname: Field::String.with_options( | ||
| getter: ->(field) { "Mr. #{field.resource.name}man" if field.resource.name.present? }, | ||
| searchable: false | ||
| ), |
There was a problem hiding this comment.
Sorry, should have been more clear: remove this example (which I find too artificial) and we can leave the one for the orders, which I think is better 🙂
| nickname: Field::String.with_options( | ||
| getter: ->(field) { "Mr. #{field.resource.name}man" if field.resource.name.present? }, | ||
| searchable: false | ||
| ), |
There was a problem hiding this comment.
Good point on searchable. Perhaps it should be false by default if getter is supplied? Otherwise it breaks the search.
| nickname: Field::String.with_options( | ||
| getter: ->(field) { "Mr. #{field.resource.name}man" if field.resource.name.present? }, | ||
| searchable: false | ||
| ), |
There was a problem hiding this comment.
@Nitr - It's a bit tricky due to how Administrate works internally. Also we tend to avoid adding dependencies like draper.
|
|
||
| module Administrate | ||
| module Field | ||
| class VirtualField < Field::Base |
There was a problem hiding this comment.
Answering on the main thread 🙂
Co-authored-by: Pablo Brasero <36066+pablobm@users.noreply.github.com>
|
Next, I would like to add documentation. |
|
@goosys - Sounds good 🙂 |
|
@goosys |
|
@Nitr class CustomerDashboard < Administrate::BaseDashboard
ATTRIBUTE_TYPES = {
orders: Field::HasMany.with_options(limit: 2, sort_by: :id),
recent_orders: Field::HasMany.with_options(
getter: ->(field){ field.resource.orders.where(created_at: [3.days.ago...]).order(created_at: :desc).limit(2) },
class_name: "Order",
foreign_key: :customer_id,
includes: :orders
),It’s quite strange at this point, but I think we can improve it going forward. What do you think? |
|
I think this is it? (Apart from the two small suggestions now). Do you think there's anything else to cover or should we go and merge? Tangentially: I need to un-deprecate the |
spec/lib/fields/base_spec.rb
Outdated
| end | ||
|
|
||
| context "when given a :getter block" do |
There was a problem hiding this comment.
This bit can be deleted as the two contexts are the same.
| end | |
| context "when given a :getter block" do | |
| end | |
| context "when given a :getter block" do |
Was this part of my suggestion the other day? 😳
Co-authored-by: Pablo Brasero <36066+pablobm@users.noreply.github.com>
I believe there are still plenty of ways we could use this, depending on new ideas. But for now, I think it’s fine to go ahead and merge it. |
|
One last thing. Sorry! 😓
Changes are at main...pablobm:administrate:goosys-issue-1586-virtual-fields. Does this make sense? If so, could you please merge my two commits into your branch? |
Goosys issue 1586 virtual fields
|
@pablobm It seems the One thing I wanted to mention—while pushing to this PR several times, I noticed that the |
|
Re: Merging at last! 🚀 |
This takes the sortable field previously explored in thoughtbot#2658. Here, we allow overriding the sorting column on a field, plus turn off sorting if it wouldn't make sense for the given data.
This takes the sortable field previously explored in #2658. Here, we allow overriding the sorting column on a field, plus turn off sorting if it wouldn't make sense for the given data.

Support for virtual fields, based on #1586.
Usage
(Note: The Sortable implementation has been split into #2659.)
What do you think? I'll make any necessary changes.
Please review it.