Class: RubyUIAdmin::BaseResource

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_ui_admin/base_resource.rb

Overview

Base class for all resources. Provides the declarative resource DSL (fields, panels, tabs, scopes, actions and filters).

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(record: nil, view: nil, user: nil, params: nil) ⇒ BaseResource

Returns a new instance of BaseResource.



78
79
80
81
82
83
# File 'lib/ruby_ui_admin/base_resource.rb', line 78

def initialize(record: nil, view: nil, user: nil, params: nil)
  @record = record
  @view = view
  @user = user
  @params = params
end

Instance Attribute Details

#paramsObject

Returns the value of attribute params.



76
77
78
# File 'lib/ruby_ui_admin/base_resource.rb', line 76

def params
  @params
end

#recordObject

Returns the value of attribute record.



76
77
78
# File 'lib/ruby_ui_admin/base_resource.rb', line 76

def record
  @record
end

#userObject

Returns the value of attribute user.



76
77
78
# File 'lib/ruby_ui_admin/base_resource.rb', line 76

def user
  @user
end

#viewObject

Returns the value of attribute view.



76
77
78
# File 'lib/ruby_ui_admin/base_resource.rb', line 76

def view
  @view
end

Class Method Details

.abstract?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/ruby_ui_admin/base_resource.rb', line 46

def abstract?
  self == RubyUIAdmin::BaseResource
end

.derive_model_classObject



40
41
42
43
44
# File 'lib/ruby_ui_admin/base_resource.rb', line 40

def derive_model_class
  name.to_s.demodulize.constantize
rescue NameError
  nil
end

.model_classObject



34
35
36
37
38
# File 'lib/ruby_ui_admin/base_resource.rb', line 34

def model_class
  return @model_class if defined?(@model_class) && @model_class

  @model_class = derive_model_class
end

.model_class=(value) ⇒ Object



30
31
32
# File 'lib/ruby_ui_admin/base_resource.rb', line 30

def model_class=(value)
  @model_class = value.is_a?(String) ? value.constantize : value
end


71
72
73
# File 'lib/ruby_ui_admin/base_resource.rb', line 71

def navigation_label
  resource_name_plural.titleize
end

.resource_nameObject

"buyer"



51
52
53
# File 'lib/ruby_ui_admin/base_resource.rb', line 51

def resource_name
  name.to_s.demodulize.underscore
end

.resource_name_pluralObject

"buyers"



56
57
58
# File 'lib/ruby_ui_admin/base_resource.rb', line 56

def resource_name_plural
  resource_name.pluralize
end

.route_keyObject

Route key used by the dynamic router and path helpers, e.g. "buyers". Derived from the RESOURCE name (not the model) so multiple resources backed by the same model get distinct routes. Override per-resource if needed.



63
64
65
# File 'lib/ruby_ui_admin/base_resource.rb', line 63

def route_key
  resource_name_plural
end

.singular_route_keyObject



67
68
69
# File 'lib/ruby_ui_admin/base_resource.rb', line 67

def singular_route_key
  resource_name
end

Instance Method Details

#action(klass, **options) ⇒ Object

---- Actions DSL ----



399
400
401
# File 'lib/ruby_ui_admin/base_resource.rb', line 399

def action(klass, **options)
  action_items << {klass: klass, options: options}
end

#action_itemsObject



403
404
405
# File 'lib/ruby_ui_admin/base_resource.rb', line 403

def action_items
  @action_items ||= []
end

#actionsObject



407
# File 'lib/ruby_ui_admin/base_resource.rb', line 407

def actions; end

#actions_for(view: nil, record: nil) ⇒ Object

Instantiated actions visible in the given view (optionally for a record).



416
417
418
419
420
421
# File 'lib/ruby_ui_admin/base_resource.rb', line 416

def actions_for(view: nil, record: nil)
  get_actions.filter_map do |entry|
    action = instantiate_action(entry, view: view, record: record)
    action if action.visible_in_view?(view)
  end
end

#apply_defaults(record) ⇒ Object

Applies field-level default: values to a new record (literal or proc).



194
195
196
197
198
199
200
201
202
# File 'lib/ruby_ui_admin/base_resource.rb', line 194

def apply_defaults(record)
  get_fields(view: :new).each do |field|
    next unless field.has_default?

    field.fill_value(record, field.default_value(record))
  end

  record
end

#base_queryObject

---- Querying ----



466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/ruby_ui_admin/base_resource.rb', line 466

def base_query
  query = model_class.all
  query = query.includes(*self.class.includes) if self.class.includes.present?

  if self.class.index_query
    query = ExecutionContext.new(
      target: self.class.index_query,
      query: query,
      params: params
    ).handle
  end

  query
end

#current_userObject

Alias so resource DSL (e.g. field defaults/options evaluated eagerly) can use current_user. user is set on hydrate.



99
# File 'lib/ruby_ui_admin/base_resource.rb', line 99

def current_user = user

#default_scope_entryObject



267
268
269
# File 'lib/ruby_ui_admin/base_resource.rb', line 267

def default_scope_entry
  get_scopes.find(&:default?)
end

#discover_associations(only: nil, except: []) ⇒ Object

Auto-declares fields from the model's associations.



156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ruby_ui_admin/base_resource.rb', line 156

def discover_associations(only: nil, except: [])
  except = Array(except).map(&:to_sym)
  only = only && Array(only).map(&:to_sym)

  model_class.reflect_on_all_associations.each do |association|
    name = association.name
    next if except.include?(name)
    next if only && !only.include?(name)

    type = association_field_type(association)
    field name, as: type if type
  end
end

#discover_columns(only: nil, except: []) ⇒ Object

Auto-declares fields from the model's database columns. discover_columns(only: %i[id name], except: %i)



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/ruby_ui_admin/base_resource.rb', line 141

def discover_columns(only: nil, except: [])
  except = Array(except).map(&:to_sym)
  only = only && Array(only).map(&:to_sym)

  model_class.columns.each do |column|
    name = column.name.to_sym
    next if except.include?(name)
    next if only && !only.include?(name)
    next if name.to_s.end_with?("_id") && only.nil? # foreign keys -> use discover_associations

    field name, as: field_type_for_column(column, name)
  end
end

#dividerObject



137
# File 'lib/ruby_ui_admin/base_resource.rb', line 137

def divider(**); end

#field(id, as: :text, **options, &block) ⇒ Object

---- Fields DSL ----



104
105
106
107
108
109
# File 'lib/ruby_ui_admin/base_resource.rb', line 104

def field(id, as: :text, **options, &block)
  field_class = Fields.field_class_for(as)
  instance = field_class.new(id, resource: self, **options, &block)
  current_collector << instance
  instance
end

#field_authorized?(field, view) ⇒ Boolean

Whether the current user may see this field in the given view.

A field is visible unless the resource's policy explicitly defines a matching rule that returns false. Candidate rules, most specific first:

index -> index_<id>? ; show -> show_<id>? ; new -> new_<id>?/edit_<id>? ;
edit -> edit_<id>? ; then the generic view_<id>? for any view.

An undefined rule means "no opinion" and the field stays visible (so enabling explicit_authorization does NOT silently hide every unconfigured field).

Returns:

  • (Boolean)


212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/ruby_ui_admin/base_resource.rb', line 212

def field_authorized?(field, view)
  return true unless RubyUIAdmin.configuration.authorization_enabled?

  policy = self.class.authorization_policy
  return true if policy.nil?

  service = Services::AuthorizationService.new(user, @record, policy_class: policy)
  rule = candidate_field_rules(field.id, view).find { |candidate| service.defines_rule?(candidate) }
  return true if rule.nil?

  service.authorize_action(rule, record: @record, raise_exception: false)
end

#field_structure(view: nil) ⇒ Object

The structured item tree (fields + panels + tabs) for the given view.



174
175
176
177
# File 'lib/ruby_ui_admin/base_resource.rb', line 174

def field_structure(view: nil)
  build_items
  filter_item_tree(@root, view)
end

#fieldsObject

Overridden by subclasses to declare fields.



171
# File 'lib/ruby_ui_admin/base_resource.rb', line 171

def fields; end

#filter(klass, **options) ⇒ Object

---- Filters DSL ----



446
447
448
# File 'lib/ruby_ui_admin/base_resource.rb', line 446

def filter(klass, **options)
  filter_items << {klass: klass, options: options}
end

#filter_itemsObject



450
451
452
# File 'lib/ruby_ui_admin/base_resource.rb', line 450

def filter_items
  @filter_items ||= []
end

#filtersObject



454
# File 'lib/ruby_ui_admin/base_resource.rb', line 454

def filters; end

#find_action_entry(key) ⇒ Object



423
424
425
# File 'lib/ruby_ui_admin/base_resource.rb', line 423

def find_action_entry(key)
  get_actions.find { |entry| entry[:klass].action_key == key.to_s }
end

#find_field(id) ⇒ Object



189
190
191
# File 'lib/ruby_ui_admin/base_resource.rb', line 189

def find_field(id)
  get_fields.find { |field| field.id == id.to_sym }
end

#find_record(id, query: nil) ⇒ Object



481
482
483
# File 'lib/ruby_ui_admin/base_resource.rb', line 481

def find_record(id, query: nil)
  (query || base_query).find(id)
end

#find_scope(key) ⇒ Object



263
264
265
# File 'lib/ruby_ui_admin/base_resource.rb', line 263

def find_scope(key)
  get_scopes.find { |scope| scope.key == key.to_s }
end

#get_actionsObject



409
410
411
412
413
# File 'lib/ruby_ui_admin/base_resource.rb', line 409

def get_actions
  @action_items = []
  actions
  action_items
end

#get_fields(view: nil) ⇒ Object

Flat list of field leaves visible in the given view (used by the index, param permitting and record filling). Filters by view visibility AND by field-level authorization.



182
183
184
185
186
187
# File 'lib/ruby_ui_admin/base_resource.rb', line 182

def get_fields(view: nil)
  build_items
  leaves = flatten_fields(@root)
  leaves = leaves.select { |field| field.visible_in_view?(view) && field.visible?(view: view) && field_authorized?(field, view) } if view
  leaves
end

#get_filtersObject



456
457
458
459
460
461
462
# File 'lib/ruby_ui_admin/base_resource.rb', line 456

def get_filters
  @filter_items = []
  filters
  filter_items.map do |entry|
    entry[:klass].new(arguments: entry.dig(:options, :arguments) || {})
  end
end

#get_scopesObject



239
240
241
242
243
244
245
246
# File 'lib/ruby_ui_admin/base_resource.rb', line 239

def get_scopes
  @scope_items = []
  @remove_scope_all_called = false
  scopes
  scope_items.map do |entry|
    entry[:klass].new(params: params || {}, default_override: entry[:options][:default])
  end
end

#hydrate(record: nil, view: nil, user: nil, params: nil) ⇒ Object



85
86
87
88
89
90
91
# File 'lib/ruby_ui_admin/base_resource.rb', line 85

def hydrate(record: nil, view: nil, user: nil, params: nil)
  @record = record unless record.nil?
  @view = view unless view.nil?
  @user = user unless user.nil?
  @params = params unless params.nil?
  self
end

#instantiate_action(entry, view: nil, record: nil, records: nil) ⇒ Object



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/ruby_ui_admin/base_resource.rb', line 427

def instantiate_action(entry, view: nil, record: nil, records: nil)
  records = records || (record ? [record] : [])
  # Expose the acted-on record as `resource.record` inside the action's `fields`
  # (a single-record/show action sees its record; a bulk action sees the
  # first selected record as a representative).
  hydrate(record: records.first) if records.first

  action = entry[:klass].new(
    view: view,
    resource: self,
    user: user,
    arguments: entry.dig(:options, :arguments) || {}
  )
  action.records = records
  action
end

#model_classObject



93
# File 'lib/ruby_ui_admin/base_resource.rb', line 93

def model_class = self.class.model_class


100
# File 'lib/ruby_ui_admin/base_resource.rb', line 100

def navigation_label = self.class.navigation_label

#new_recordObject



485
486
487
# File 'lib/ruby_ui_admin/base_resource.rb', line 485

def new_record
  model_class.new
end

#panel(name = nil, **opts, &block) ⇒ Object

Groups fields in a card-like panel. Accepts the name either positionally (panel "X") or as a keyword (panel name: "X") for Avo compatibility.



113
114
115
116
117
118
119
# File 'lib/ruby_ui_admin/base_resource.rb', line 113

def panel(name = nil, **opts, &block)
  name = opts[:name] if name.nil?
  container = Structure::Panel.new(name)
  current_collector << container
  within(container.items, &block)
  container
end

#record_title(target = record) ⇒ Object



489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/ruby_ui_admin/base_resource.rb', line 489

def record_title(target = record)
  return nil if target.nil?

  title_attr = self.class.title

  if title_attr.respond_to?(:call)
    ExecutionContext.new(target: title_attr, record: target, resource: self).handle
  elsif target.respond_to?(title_attr)
    target.public_send(title_attr)
  else
    "#{model_class.model_name.human} ##{target.id}"
  end
end

#remove_scope_allObject

Callable inside def scopes to hide the "All" tab. The class attribute self.remove_scope_all = true also works.



250
251
252
# File 'lib/ruby_ui_admin/base_resource.rb', line 250

def remove_scope_all
  @remove_scope_all_called = true
end

#remove_scope_all?Boolean

Returns:

  • (Boolean)


254
255
256
257
# File 'lib/ruby_ui_admin/base_resource.rb', line 254

def remove_scope_all?
  get_scopes # ensures `scopes` ran (which may call `remove_scope_all`)
  @remove_scope_all_called || self.class.remove_scope_all
end

#resource_nameObject



96
97
98
# File 'lib/ruby_ui_admin/base_resource.rb', line 96

def resource_name = self.class.resource_name
# Alias so resource DSL (e.g. field defaults/options evaluated eagerly) can use
# `current_user`. `user` is set on hydrate.

#route_keyObject



94
# File 'lib/ruby_ui_admin/base_resource.rb', line 94

def route_key = self.class.route_key

#scope(klass, **options) ⇒ Object

scope Klass or scope Klass, default: true (mark default at attachment time).



228
229
230
# File 'lib/ruby_ui_admin/base_resource.rb', line 228

def scope(klass, **options)
  scope_items << {klass: klass, options: options}
end

#scope_itemsObject



232
233
234
# File 'lib/ruby_ui_admin/base_resource.rb', line 232

def scope_items
  @scope_items ||= []
end

#scopesObject

Overridden by subclasses to declare named index scopes.



237
# File 'lib/ruby_ui_admin/base_resource.rb', line 237

def scopes; end

#scopes?Boolean

Returns:

  • (Boolean)


259
260
261
# File 'lib/ruby_ui_admin/base_resource.rb', line 259

def scopes?
  get_scopes.any?
end

#singular_route_keyObject



95
# File 'lib/ruby_ui_admin/base_resource.rb', line 95

def singular_route_key = self.class.singular_route_key

#tab(name, description: nil, &block) ⇒ Object

A single tab inside a tabs block.



130
131
132
133
134
135
# File 'lib/ruby_ui_admin/base_resource.rb', line 130

def tab(name, description: nil, &block)
  container = Structure::Tab.new(name, description: description)
  current_collector << container
  within(container.items, &block)
  container
end

#tabs(&block) ⇒ Object

Declares a set of tabs. Use tab inside the block.



122
123
124
125
126
127
# File 'lib/ruby_ui_admin/base_resource.rb', line 122

def tabs(&block)
  group = Structure::TabGroup.new
  current_collector << group
  within(group.tabs, &block)
  group
end