Skip to content
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
88 changes: 76 additions & 12 deletions app/controllers/administrate/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def index
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.

filters = Administrate::Search.new(scoped_resource, dashboard, search_term).valid_filters

render locals: {
Expand All @@ -22,52 +23,69 @@ 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
if resource.save(context: validation_contexts_on_create(resource))
yield(resource) if block_given?
redirect_to(
after_resource_created_path(resource),
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

def update
if requested_resource.update(resource_params)
requested_resource.assign_attributes(resource_params)

if requested_resource.save(context: validation_contexts_on_update(requested_resource))
redirect_to(
after_resource_updated_path(requested_resource),
notice: translate_with_resource("update.success"),
status: :see_other
)
else
page = Administrate::Page::Form.new(dashboard, requested_resource)
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 @@ -182,19 +200,31 @@ 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

# Override this method to specify custom lookup behavior.
# This will be used to set the resource for the `show`, `edit`, `update` and `destroy` actions.
#
# @param param [ActiveSupport::Parameter]
# @return [ActiveRecord::Base]
def find_resource(param)
scoped_resource.find(param)
end

# Override this if you have certain roles that require a subset.
# This will be used in all actions except for the `new` and `create` actions.
#
# @return [ActiveRecord::Relation]
def scoped_resource
resource_class.default_scoped
end
Expand Down Expand Up @@ -278,6 +308,13 @@ def new_resource(params = {})
end
helper_method :new_resource

# Override this if you want to authorize the resource differently.
# This will be used to authorize the resource for the all actions without `index`.
# In the case of `index`, it is used to authorize the resource class.
#
# @param resource [ActiveRecord::Base]
# @return [ActiveRecord::Base]
# @raise [Administrate::NotAuthorizedError] if the resource is not authorized.
def authorize_resource(resource)
if authorized_action?(resource, action_name)
resource
Expand All @@ -289,6 +326,33 @@ 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

# Override this if you want to provide additional validation contexts.
#
# @param resource [ActiveRecord::Base] The resource to be validated.
# @return [Array<Symbol>] The validation contexts to be used.
def validation_contexts_on_create(resource)
default_validation_contexts(resource)
end

# Override this if you want to provide additional validation contexts.
#
# @param resource [ActiveRecord::Base] The resource to be validated.
# @return [Array<Symbol>] The validation contexts to be used.
def validation_contexts_on_update(resource)
default_validation_contexts(resource)
end

def default_validation_contexts(resource)
resource.new_record? ? [:create] : [:update]
end

def paginate_resources(resources)
resources.page(params[:_page]).per(records_per_page)
end
Expand Down
45 changes: 41 additions & 4 deletions docs/customizing_controller_actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@ class Admin::FoosController < Admin::ApplicationController
# end

# Override this method to specify custom lookup behavior.
# This will be used to set the resource for the `show`, `edit`, and `update`
# actions.
# This will be used to set the resource for the `show`, `edit`, `update` and `destroy` actions.
#
# def find_resource(param)
# Foo.find_by!(slug: param)
# end

# Override this if you have certain roles that require a subset
# this will be used to set the records shown on the `index` action.
# Override this if you have certain roles that require a subset.
# This will be used in all actions except for the `new` and `create` actions
#
# def scoped_resource
# if current_user.super_admin?
Expand All @@ -41,6 +40,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 Expand Up @@ -107,3 +117,30 @@ def create
end
end
```

## Validation Contexts

You can customize the context when saving a resource in the controller.
For example, with the following settings, regular admins are required to input a name, but super admins can update the resource *without* a name.

```ruby
# Model
validates :name, presence: true, on: :save_by_regular_admin

# Controller
def validation_contexts_on_create(resource)
if current_user.super_admin?
super + [:save_by_super_admin]
else
super + [:save_by_regular_admin]
end
end

def validation_contexts_on_update(resource)
if current_user.super_admin?
super + [:save_by_super_admin]
else
super + [:save_by_regular_admin]
end
end
```
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 @@ -114,6 +114,7 @@ def required?
end

attr_reader :attribute, :data, :options, :page, :resource
attr_accessor :context
end
end
end
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 @@ -106,12 +109,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 @@ -33,14 +33,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
14 changes: 13 additions & 1 deletion lib/administrate/page/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,23 @@ 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)
field = dashboard.attribute_type_for(attribute_name)
field.new(attribute_name, nil, page, resource: resource)
field.new(attribute_name, nil, page, resource: resource).tap do |f|
f.context = context
end
end

def get_attribute_value(resource, attribute_name)
resource.public_send(attribute_name)
end

attr_reader :dashboard, :options
Expand Down
Loading