-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for virtual fields #2658
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
end Should 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
lib/administrate/field/base.rb
Outdated
else | ||
if data.nil? | ||
resource&.public_send(attribute) | ||
else | ||
data | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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]
)
.freeze
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like searchable: false
is excess. If getter has been provided it makes field as unsearchable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you thought about using decorators(like draper) for virtual fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Nitr - It's a bit tricky due to how Administrate works internally. Also we tend to avoid adding dependencies like draper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
module Administrate | ||
module Field | ||
class VirtualField < Field::Base |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Answering on the main thread 🙂
a83e2ff
to
a575bde
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
+end
Still 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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! 🚀 |
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.