Skip to content
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

Add context to Page #2457

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 43 additions & 12 deletions app/controllers/administrate/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ class ApplicationController < ActionController::Base
def index
authorize_resource(resource_class)
search_term = params[:search].to_s.strip
resources = filter_resources(scoped_resource, search_term: search_term)
authorized_scope = authorize_scope(scoped_resource)
resources = filter_resources(authorized_scope, search_term: search_term)
resources = apply_collection_includes(resources)
resources = order.apply(resources)
resources = paginate_resources(resources)
page = Administrate::Page::Collection.new(dashboard, order: order)
page.context = self
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following on my comment about contextualize_resource, I wonder if this is what it should be doing instead? Acting on the page instead of the resource here:

Suggested change
page.context = self
contextualize_page(page)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Controller object is almost essential as a Context for the Page, so how about passing it to the initializer? Like the following:

page = Administrate::Page::Collection.new(dashboard, context: self, order: order)
or
page = Administrate::Page::Collection.new(dashboard, controller: self, order: order)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially that makes sense, but then the developers wouldn't be able to alter the context easily with a hook like contextualize_page. Having said that, perhaps there isn't a use case for that...? I have no idea yet. I'm experimenting now to see the possibilities of your PR.


render locals: {
resources: resources,
Expand All @@ -20,28 +22,39 @@ def index
end

def show
page = Administrate::Page::Show.new(dashboard, requested_resource)
page.context = self
render locals: {
page: Administrate::Page::Show.new(dashboard, requested_resource)
page: page
}
end

def new
resource = new_resource
authorize_resource(resource)
resource = new_resource.tap do |resource|
authorize_resource(resource)
contextualize_resource(resource)
end

page = Administrate::Page::Form.new(dashboard, resource)
page.context = self
render locals: {
page: Administrate::Page::Form.new(dashboard, resource)
page: page
}
end

def edit
page = Administrate::Page::Form.new(dashboard, requested_resource)
page.context = self
render locals: {
page: Administrate::Page::Form.new(dashboard, requested_resource)
page: page
}
end

def create
resource = new_resource(resource_params)
authorize_resource(resource)
resource = new_resource(resource_params).tap do |resource|
authorize_resource(resource)
contextualize_resource(resource)
end

if resource.save
yield(resource) if block_given?
Expand All @@ -50,8 +63,10 @@ def create
notice: translate_with_resource("create.success")
)
else
page = Administrate::Page::Form.new(dashboard, resource)
page.context = self
render :new, locals: {
page: Administrate::Page::Form.new(dashboard, resource)
page: page
}, status: :unprocessable_entity
end
end
Expand All @@ -64,8 +79,10 @@ def update
status: :see_other
)
else
page = Administrate::Page::Form.new(dashboard, requested_resource)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this one missing the context assignment?

Suggested change
page = Administrate::Page::Form.new(dashboard, requested_resource)
page = Administrate::Page::Form.new(dashboard, requested_resource)
page.context = self

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right. Sorry.

page.context = self
render :edit, locals: {
page: Administrate::Page::Form.new(dashboard, requested_resource)
page: page
}, status: :unprocessable_entity
end
end
Expand Down Expand Up @@ -178,17 +195,24 @@ def sorting_params
end

def dashboard
@dashboard ||= dashboard_class.new
@dashboard ||= dashboard_class.new.tap do |d|
d.context = self
end
end

def requested_resource
@requested_resource ||= find_resource(params[:id]).tap do |resource|
authorize_resource(resource)
contextualize_resource(resource)
end
end

def find_resource(param)
scoped_resource.find(param)
authorize_scope(scoped_resource).find(param)
end

def authorize_scope(scope)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this introduction of authorize_scope is off-topic in this PR? I can see the value of separating this concern, but it's different from the goal of adding context to the pages. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right, this change is off-topic. I appreciate your accurate understanding of my intention.
However, I thought this change was also necessary for effective use of the contextualize_resource method, so I included it in the same PR. If necessary, I can split the PR.

scope
end

def scoped_resource
Expand Down Expand Up @@ -285,6 +309,13 @@ def authorize_resource(resource)
end
end

# Override this if you want to contextualize the resource differently.
#
# @param resource A resource to be contextualized.
# @return nothing
def contextualize_resource(resource)
end

def paginate_resources(resources)
resources.page(params[:_page]).per(records_per_page)
end
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/concerns/administrate/punditize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def policy_namespace
[]
end

def scoped_resource
namespaced_scope = policy_namespace + [super]
def authorize_scope(scope)
namespaced_scope = policy_namespace + [scope]
policy_scope!(pundit_user, namespaced_scope)
end

Expand Down
11 changes: 11 additions & 0 deletions docs/customizing_controller_actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ class Admin::FoosController < Admin::ApplicationController
# resource_class.with_less_stuff
# end
# end


# Override this if you want to contextualize the resource differently.
# This will be used to contextualize the resource for the all actions without `index`.
#
# def contextualize_resource(resource)
# case action_name
# when "new", "create"
# resource.author = current_user
# end
# end
end
```

Expand Down
4 changes: 3 additions & 1 deletion lib/administrate/base_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def specific_form_attributes_for(action)
end

def permitted_attributes(action = nil)
attributes = form_attributes action
attributes = form_attributes(action)

if attributes.is_a? Hash
attributes = attributes.values.flatten
Expand Down Expand Up @@ -126,6 +126,8 @@ def item_associations
attribute_associated attributes
end

attr_accessor :context

private

def attribute_not_found_message(attr)
Expand Down
4 changes: 3 additions & 1 deletion lib/administrate/field/associative.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ def html_controller
private

def associated_dashboard
"#{associated_class_name}Dashboard".constantize.new
"#{associated_class_name}Dashboard".constantize.new.tap do |d|
d.context = context
end
end

def primary_key
Expand Down
1 change: 1 addition & 0 deletions lib/administrate/field/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def required?
end

attr_reader :attribute, :data, :options, :page, :resource
attr_accessor :context
end
end
end
2 changes: 1 addition & 1 deletion lib/administrate/field/belongs_to.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def include_blank_option
private

def candidate_resources
scope = options[:scope] ? options[:scope].call : associated_class.all
scope = options[:scope] ? options[:scope].call(self) : associated_class.all

order = options.delete(:order)
order ? scope.reorder(order) : scope
Expand Down
16 changes: 9 additions & 7 deletions lib/administrate/field/has_many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ def associated_collection(order = self.order)
associated_dashboard,
order: order,
collection_attributes: options[:collection_attributes]
)
).tap do |page|
page.context = context
page.dashboard_context = context
end
end

def attribute_key
Expand Down Expand Up @@ -97,12 +100,11 @@ def includes
end

def candidate_resources
if options.key?(:includes)
includes = options.fetch(:includes)
associated_class.includes(*includes).all
else
associated_class.all
end
scope = options[:scope] ? options[:scope].call(self) : associated_class.all
scope = scope.includes(*options.fetch(:includes)) if options.key?(:includes)

order = options.delete(:order)
order ? scope.reorder(order) : scope
end

def display_candidate_resource(resource)
Expand Down
10 changes: 8 additions & 2 deletions lib/administrate/field/has_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@ def nested_form
@nested_form ||= Administrate::Page::Form.new(
resolver.dashboard_class.new,
data || resolver.resource_class.new
)
).tap do |page|
page.context = context
page.dashboard_context = context
end
end

def nested_show
@nested_show ||= Administrate::Page::Show.new(
resolver.dashboard_class.new,
data || resolver.resource_class.new
)
) do |page|
page.context = context
page.dashboard_context = context
end
end

def linkable?
Expand Down
4 changes: 3 additions & 1 deletion lib/administrate/field/polymorphic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ def selected_global_id
private

def associated_dashboard(klass = data.class)
"#{klass.name}Dashboard".constantize.new
"#{klass.name}Dashboard".constantize.new.tap do |d|
d.context = context
end
end

def classes
Expand Down
10 changes: 9 additions & 1 deletion lib/administrate/page/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ def item_associations
dashboard.try(:item_associations) || []
end

attr_accessor :context

def dashboard_context=(context)
dashboard.context = context
end

private

def attribute_field(dashboard, resource, attribute_name, page)
value = get_attribute_value(resource, attribute_name)
field = dashboard.attribute_type_for(attribute_name)
field.new(attribute_name, value, page, resource: resource)
field.new(attribute_name, value, page, resource: resource).tap do |f|
f.context = context
end
end

def get_attribute_value(resource, attribute_name)
Expand Down
20 changes: 18 additions & 2 deletions spec/controllers/admin/customers_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
describe Admin::CustomersController, type: :controller do
describe "GET index" do
it "passes all customers to the view" do
customer = create(:customer)
customers = create_list(:customer, 5)

locals = capture_view_locals { get :index }
expect(locals[:resources]).to eq([customer])
expect(locals[:resources]).to eq(customers)
end

it "applies any scope overrides" do
Expand Down Expand Up @@ -47,6 +47,22 @@
expect(locals[:resources].map(&:id)).to eq customers.map(&:id).sort
end

context "when the user is an admin" do
controller(Admin::CustomersController) do
def authenticate_admin
@current_user = Customer.last
end
end

it "passes one customers to the view" do
_other_customers = create_list(:customer, 5)
customer = create(:customer)

locals = capture_view_locals { get :index }
expect(locals[:resources]).to eq([customer])
end
end

context "with alternate sorting attributes" do
controller(Admin::CustomersController) do
def default_sorting_attribute
Expand Down
Loading
Loading