Module: ForestAdminDatasourceCustomizer::DSL::CollectionHelpers

Included in:
CollectionCustomizer
Defined in:
lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb

Overview

CollectionHelpers provides Rails-like DSL methods for collection customization These methods are included in CollectionCustomizer to provide a more idiomatic Ruby API rubocop:disable Naming/PredicatePrefix

Instance Method Summary collapse

Instance Method Details

#action(name, scope: :single, &block) ⇒ Object

Define a custom action with a fluent DSL

Examples:

Simple action

action :approve, scope: :single do
  execute do
    success "Approved!"
  end
end

Action with form

action :export, scope: :global do
  description "Export all customers"
  generates_file!

  form do
    field :format, type: :string, widget: 'Dropdown',
          options: [{ label: 'CSV', value: 'csv' }]
  end

  execute do
    format = form_value(:format)
    file content: generate_csv, name: "export.#{format}"
  end
end

Parameters:

  • name (String, Symbol)

    action name

  • scope (Symbol) (defaults to: :single)

    action scope (:single, :bulk, :global)

  • block (Proc)

    action definition block

Raises:

  • (ArgumentError)


72
73
74
75
76
77
78
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 72

def action(name, scope: :single, &block)
  raise ArgumentError, 'Block is required for action' unless block

  builder = ActionBuilder.new(scope: scope)
  builder.instance_eval(&block)
  add_action(name.to_s, builder.to_action)
end

#after(operation, &block) ⇒ Object

Add an after hook for an operation

Examples:

after :create do |context|
  # Send notification after create
end

Parameters:

  • operation (String, Symbol)

    operation name (:create, :update, :delete, :list, :aggregate)

  • block (Proc)

    hook handler

Raises:

  • (ArgumentError)


124
125
126
127
128
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 124

def after(operation, &block)
  raise ArgumentError, 'Block is required for after hook' unless block

  add_hook('After', operation.to_s.capitalize, &block)
end

#before(operation, &block) ⇒ Object

Add a before hook for an operation

Examples:

before :create do |context|
  # Validate or transform data before create
end

Parameters:

  • operation (String, Symbol)

    operation name (:create, :update, :delete, :list, :aggregate)

  • block (Proc)

    hook handler

Raises:

  • (ArgumentError)


109
110
111
112
113
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 109

def before(operation, &block)
  raise ArgumentError, 'Block is required for before hook' unless block

  add_hook('Before', operation.to_s.capitalize, &block)
end

#belongs_to(name, collection: nil, foreign_key: nil) ⇒ Object

ActiveRecord-style belongs_to relation

Examples:

belongs_to :author, foreign_key: :author_id

Parameters:

  • name (String, Symbol)

    relation name

  • collection (String, Symbol) (defaults to: nil)

    target collection name (defaults to pluralized name)

  • foreign_key (String, Symbol) (defaults to: nil)

    foreign key field

Raises:

  • (ArgumentError)


138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 138

def belongs_to(name, collection: nil, foreign_key: nil)
  raise ArgumentError, 'Relation name is required for belongs_to' if name.nil? || name.to_s.empty?

  collection_name = collection&.to_s || "#{name}s"
  foreign_key_name = foreign_key&.to_s || "#{name}_id"

  add_many_to_one_relation(
    name.to_s,
    collection_name,
    { foreign_key: foreign_key_name }
  )
end

#chart(name, &block) ⇒ Object

Add a chart at the collection level with a cleaner syntax

Examples:

Simple value chart

chart :num_records do
  value 1234
end

Distribution chart

chart :status_distribution do
  distribution({
    'Active' => 150,
    'Inactive' => 50
  })
end

Chart with context

chart :monthly_stats do |context|
  # Access the collection and calculate stats
  value calculated_value
end

Parameters:

  • name (String, Symbol)

    chart name

  • block (Proc)

    chart definition block

Raises:

  • (ArgumentError)


263
264
265
266
267
268
269
270
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 263

def chart(name, &block)
  raise ArgumentError, 'Block is required for chart' unless block

  add_chart(name.to_s) do |context, result_builder|
    builder = DSL::ChartBuilder.new(context, result_builder)
    builder.instance_eval(&block)
  end
end

#computed_field(name, type:, depends_on: [], default: nil, enum_values: nil, &block) ⇒ Object

Define a computed field with a cleaner syntax

Examples:

Simple computed field

computed_field :full_name, type: 'String', depends_on: [:first_name, :last_name] do |records|
  records.map { |r| "#{r['first_name']} #{r['last_name']}" }
end

Computed relation

computed_field :related_items, type: ['RelatedItem'], depends_on: [:id] do |records, context|
  records.map { |r| fetch_related(r['id']) }
end

Parameters:

  • name (String, Symbol)

    field name

  • type (String, Array<String>)

    field type (or array of types for relations)

  • depends_on (Array<String, Symbol>) (defaults to: [])

    fields this computation depends on

  • default (Object) (defaults to: nil)

    default value

  • enum_values (Array) (defaults to: nil)

    enum values if type is enum

  • block (Proc)

    computation block receiving (records, context)

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 29

def computed_field(name, type:, depends_on: [], default: nil, enum_values: nil, &block)
  raise ArgumentError, 'Block is required for computed field' unless block

  add_field(
    name.to_s,
    Decorators::Computed::ComputedDefinition.new(
      column_type: type,
      dependencies: Array(depends_on).map(&:to_s),
      values: block,
      default_value: default,
      enum_values: enum_values
    )
  )
end

#has_many(name, collection: nil, origin_key: nil, foreign_key: nil, through: nil) ⇒ Object

ActiveRecord-style has_many relation

Examples:

has_many :books, origin_key: :author_id

Parameters:

  • name (String, Symbol)

    relation name

  • collection (String, Symbol) (defaults to: nil)

    target collection name (defaults to name)

  • origin_key (String, Symbol) (defaults to: nil)

    origin key field

  • foreign_key (String, Symbol) (defaults to: nil)

    foreign key field (for many-to-many)

  • through (String, Symbol) (defaults to: nil)

    through collection (for many-to-many)

Raises:

  • (ArgumentError)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 161

def has_many(name, collection: nil, origin_key: nil, foreign_key: nil, through: nil)
  raise ArgumentError, 'Relation name is required for has_many' if name.nil? || name.to_s.empty?

  collection_name = collection&.to_s || name.to_s

  if through
    # Many-to-many relation
    add_many_to_many_relation(
      name.to_s,
      collection_name,
      through.to_s,
      {
        origin_key: origin_key&.to_s,
        foreign_key: foreign_key&.to_s
      }.compact
    )
  else
    # One-to-many relation
    add_one_to_many_relation(
      name.to_s,
      collection_name,
      { origin_key: origin_key&.to_s }.compact
    )
  end
end

#has_one(name, collection: nil, origin_key: nil) ⇒ Object

ActiveRecord-style has_one relation

Examples:

has_one :profile, origin_key: :user_id

Parameters:

  • name (String, Symbol)

    relation name

  • collection (String, Symbol) (defaults to: nil)

    target collection name (defaults to pluralized name)

  • origin_key (String, Symbol) (defaults to: nil)

    origin key field

Raises:

  • (ArgumentError)


195
196
197
198
199
200
201
202
203
204
205
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 195

def has_one(name, collection: nil, origin_key: nil)
  raise ArgumentError, 'Relation name is required for has_one' if name.nil? || name.to_s.empty?

  collection_name = collection&.to_s || "#{name}s"

  add_one_to_one_relation(
    name.to_s,
    collection_name,
    { origin_key: origin_key&.to_s }.compact
  )
end

#hide_fields(*field_names) ⇒ Object

Hide fields from the schema

Examples:

hide_fields :internal_id, :secret_token

Parameters:

  • field_names (Array<String, Symbol>)

    fields to hide

Raises:

  • (ArgumentError)


234
235
236
237
238
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 234

def hide_fields(*field_names)
  raise ArgumentError, 'At least one field name is required for hide_fields' if field_names.empty?

  remove_field(*field_names.map(&:to_s))
end

#segment(name, &block) ⇒ Object

Define a segment with a cleaner syntax

Examples:

Static segment

segment 'Active users' do
  { field: 'is_active', operator: 'Equal', value: true }
end

Dynamic segment

segment 'High value customers' do
  { field: 'lifetime_value', operator: 'GreaterThan', value: 10000 }
end

Parameters:

  • name (String)

    segment name

  • block (Proc)

    block returning condition tree

Raises:

  • (ArgumentError)


94
95
96
97
98
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 94

def segment(name, &block)
  raise ArgumentError, 'Block is required for segment' unless block

  add_segment(name, &block)
end

#validates(field_name, operator, value = nil) ⇒ Object

Validate a field with a cleaner syntax

Examples:

Simple validation

validates :email, :email
validates :age, :greater_than, 18

Parameters:

  • field_name (String, Symbol)

    field name

  • operator (String, Symbol)

    validation operator

  • value (Object) (defaults to: nil)

    validation value (optional)

Raises:

  • (ArgumentError)


216
217
218
219
220
221
222
223
224
225
226
# File 'lib/forest_admin_datasource_customizer/dsl/helpers/collection_helpers.rb', line 216

def validates(field_name, operator, value = nil)
  raise ArgumentError, 'Field name is required for validates' if field_name.nil? || field_name.to_s.empty?
  raise ArgumentError, 'Operator is required for validates' if operator.nil? || operator.to_s.empty?

  # Convert snake_case to PascalCase
  operator_str = operator.to_s
                         .split('_')
                         .map(&:capitalize)
                         .join
  add_field_validation(field_name.to_s, operator_str, value)
end