Module: CafeCar::Controller

Extended by:
ActiveSupport::Concern
Includes:
Authentication, AssociationAuthorization, Filtering, Pundit::Authorization
Included in:
ApplicationController, DashboardsController, ExamplesController, SessionsController
Defined in:
lib/cafe_car/controller.rb

Defined Under Namespace

Modules: AssociationAuthorization, Filtering

Constant Summary collapse

INDEX_VIEWS =
%w[table grid chart].freeze

Constants included from Filtering

Filtering::CONTROL_PARAMS

Instance Method Summary collapse

Instance Method Details

#batchObject

Apply a bulk action, named by params[:bulk_action], to the selected records. The action name is derived, not registered: the model policy's permitted_bulk_actions is the whitelist (a name outside it is a bad request), name? is the per-record authorization predicate, and name! the model bang method applied. Every record is authorized ON ITS OWN — the candidate set is first narrowed to the policy scope (rows the user may see), then each is checked against name?; unauthorized rows are skipped, never bulk-bypassed. That per-record check is the security boundary, so Pundit's blanket verify_authorized is satisfied by skip_authorization after the fact.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/cafe_car/controller.rb', line 131

def batch
  skip_authorization # authorization is per-record below, not one blanket check
  action = permitted_bulk_action(params[:bulk_action])
  unless action
    skip_policy_scope # no candidate query on the reject path
    return head(:bad_request)
  end

  records = policy_scope(model).where(id: Array(params[:ids]))
  batched = records.select { |record| action_allowed?(record, action) }
  batched.each { |record| record.public_send("#{action}!") }

  redirect_to url_for(action: :index), success: batch_notice(action, batched.size)
end

#collection_actionObject

Run a policy-declared custom action over the collection: POST //actions/:collection_action. Same derivation as #member_action — permitted_collection_actions whitelists, name? (asked of the model class) authorizes — then name! runs on the #filtered_scope, which ActiveRecord delegates to a class method within that scoping. It runs over the currently-viewed, filtered set (the button carries the active filters, its label shows the count) — "Publish all" acts on exactly the records the user is looking at. A host override (a public controller method of the action's name) scopes its own query.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/cafe_car/controller.rb', line 174

def collection_action
  skip_authorization # authorized via the action's own predicate below
  name = permitted_custom_action(params[:collection_action], policy(model.new).attributes.actions.collection)
  unless name
    skip_policy_scope # no query on the reject path
    return head(:not_found)
  end

  authorize_action! model, name
  return public_send(name) if respond_to?(name)

  filtered_scope.public_send("#{name}!")
  redirect_to url_for(action: :index), success: action_notice(:collection_action, name)
end

#createObject



107
108
109
110
# File 'lib/cafe_car/controller.rb', line 107

def create
  run_callbacks(:create) { object.save! }
  respond_with object
end

#destroyObject



117
118
119
120
# File 'lib/cafe_car/controller.rb', line 117

def destroy
  run_callbacks(:destroy) { object.destroy! }
  respond_with object
end

#editObject



105
# File 'lib/cafe_car/controller.rb', line 105

def edit  = respond_with object

#indexObject



102
# File 'lib/cafe_car/controller.rb', line 102

def index = respond_with objects

#member_actionObject

Run a policy-declared custom action on one record: POST //:id/actions/:member_action. The name resolves through the model policy's permitted_member_actions whitelist (anything else is a 404), its name? predicate authorizes, then — by convention — the record's name! bang method runs. A host overrides the behavior by defining a public controller method of the action's name; it takes over after authorization and owns the response.



153
154
155
156
157
158
159
160
161
162
163
# File 'lib/cafe_car/controller.rb', line 153

def member_action
  skip_authorization # authorized via the action's own predicate below
  name = permitted_custom_action(params[:member_action], policy(object).attributes.actions.member)
  return head(:not_found) unless name

  authorize_action! object, name
  return public_send(name) if respond_to?(name)

  object.public_send("#{name}!")
  redirect_back_or_to href_for(object), success: action_notice(:member_action, name)
end

#newObject



104
# File 'lib/cafe_car/controller.rb', line 104

def new   = respond_with object

#optionsObject

JSON typeahead feed for a searchable association select (Tom Select). Returns [{value, text}] for the model, filtered by the ?q= keyword search and capped at max_collection_options — so an association field can reach records PAST the render cap. Authorized twice: index? gates list access at all, and policy_scope narrows rows to those the user may see (never leaking hidden ones).



194
195
196
197
198
199
200
# File 'lib/cafe_car/controller.rb', line 194

def options
  authorize model, :index?
  scope = policy_scope(model)
  scope = scope.query([ search_term ]) if search_term
  records = scope.limit(CafeCar.max_collection_options)
  render json: records.map { |record| { value: record.id, text: option_label(record) } }
end

#respond_with(*resources, **options, &block) ⇒ Object



202
203
204
# File 'lib/cafe_car/controller.rb', line 202

def respond_with(*resources, **options, &block)
  super(*namespace, *resources, **options, &block)
end

#showObject



103
# File 'lib/cafe_car/controller.rb', line 103

def show  = respond_with object

#updateObject



112
113
114
115
# File 'lib/cafe_car/controller.rb', line 112

def update
  run_callbacks(:update) { object.save! }
  respond_with object
end