Class: ElaineCrud::FieldConfiguration

Inherits:
Object
  • Object
show all
Defined in:
lib/elaine_crud/field_configuration.rb

Overview

Configuration class for individual field customization Supports both hash-style and block-style DSL configuration

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(field_name, **options) ⇒ FieldConfiguration

Returns a new instance of FieldConfiguration.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/elaine_crud/field_configuration.rb', line 12

def initialize(field_name, **options)
  @field_name = field_name

  # Set defaults
  @title = options.fetch(:title, field_name.to_s.humanize)
  @description = options.fetch(:description, nil)
  @readonly = options.fetch(:readonly, false)
  @default_value = options.fetch(:default_value, nil)
  @display_callback = options.fetch(:display_as, nil)
  @edit_callback = options.fetch(:edit_as, nil)
  @edit_partial = options.fetch(:edit_partial, nil)
  @options = options.fetch(:options, nil)
  @foreign_key_config = options.fetch(:foreign_key, nil)
  @has_many_config = options.fetch(:has_many, nil)
  @has_one_config = options.fetch(:has_one, nil)
  @habtm_config = options.fetch(:habtm, nil)
  @visible = options.fetch(:visible, nil)
  @grid_column_span = options.fetch(:grid_column_span, nil)
  @searchable = options.fetch(:searchable, nil)
  @filterable = options.fetch(:filterable, nil)
  @filter_type = options.fetch(:filter_type, nil)
  @nested_create_config = options.fetch(:nested_create, nil)
end

Instance Attribute Details

#default_value(value = nil) ⇒ Object

Returns the value of attribute default_value.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def default_value
  @default_value
end

#description(value = nil) ⇒ Object

Returns the value of attribute description.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def description
  @description
end

#display_callbackObject

Returns the value of attribute display_callback.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def display_callback
  @display_callback
end

#edit_callbackObject

Returns the value of attribute edit_callback.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def edit_callback
  @edit_callback
end

#edit_partial(partial_path = nil) ⇒ Object

Returns the value of attribute edit_partial.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def edit_partial
  @edit_partial
end

#field_nameObject

Returns the value of attribute field_name.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def field_name
  @field_name
end

#filter_type(value = nil) ⇒ Object

Returns the value of attribute filter_type.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def filter_type
  @filter_type
end

#filterable(value = nil) ⇒ Object

Returns the value of attribute filterable.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def filterable
  @filterable
end

#foreign_key_configObject

Returns the value of attribute foreign_key_config.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def foreign_key_config
  @foreign_key_config
end

#grid_column_span(value = nil) ⇒ Object

Returns the value of attribute grid_column_span.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def grid_column_span
  @grid_column_span
end

#habtm_configObject

Returns the value of attribute habtm_config.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def habtm_config
  @habtm_config
end

#has_many_configObject

Returns the value of attribute has_many_config.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def has_many_config
  @has_many_config
end

#has_one_configObject

Returns the value of attribute has_one_config.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def has_one_config
  @has_one_config
end

#nested_create_configObject

Returns the value of attribute nested_create_config.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def nested_create_config
  @nested_create_config
end

#options(list = nil) ⇒ Object

Returns the value of attribute options.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def options
  @options
end

#readonly(value = nil) ⇒ Object

Returns the value of attribute readonly.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def readonly
  @readonly
end

#searchable(value = nil) ⇒ Object

Returns the value of attribute searchable.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def searchable
  @searchable
end

#title(value = nil) ⇒ Object

Block-style DSL methods



37
38
39
# File 'lib/elaine_crud/field_configuration.rb', line 37

def title
  @title
end

#visible(value = nil) ⇒ Object

Returns the value of attribute visible.



7
8
9
# File 'lib/elaine_crud/field_configuration.rb', line 7

def visible
  @visible
end

Instance Method Details

#default_input_type(column_type) ⇒ Object

Helper method to get field input type for default rendering



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/elaine_crud/field_configuration.rb', line 317

def default_input_type(column_type)
  # TODO: Implement default input type mapping
  # Map ActiveRecord column types to HTML input types:
  # - :string, :text -> text_field / text_area
  # - :integer, :decimal, :float -> number_field
  # - :boolean -> check_box
  # - :date -> date_field
  # - :datetime, :timestamp -> datetime_local_field
  # - etc.
  case column_type
  when :string then :text_field
  when :text then :text_area
  when :integer, :decimal, :float then :number_field
  when :boolean then :check_box
  when :date then :date_field
  when :datetime, :timestamp then :datetime_local_field
  else :text_field
  end
end

#display_as(callback = nil, &block) ⇒ Object



57
58
59
60
# File 'lib/elaine_crud/field_configuration.rb', line 57

def display_as(callback = nil, &block)
  return @display_callback if callback.nil? && block.nil?
  @display_callback = callback || block
end

#edit_as(callback = nil, &block) ⇒ Object



62
63
64
65
# File 'lib/elaine_crud/field_configuration.rb', line 62

def edit_as(callback = nil, &block)
  return @edit_callback if callback.nil? && block.nil?
  @edit_callback = callback || block
end

#foreign_key(**config) ⇒ Object



77
78
79
80
# File 'lib/elaine_crud/field_configuration.rb', line 77

def foreign_key(**config)
  return @foreign_key_config if config.empty?
  @foreign_key_config = config
end

#foreign_key_options(controller_instance) ⇒ Object

Method to get foreign key options for dropdowns



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/elaine_crud/field_configuration.rb', line 249

def foreign_key_options(controller_instance)
  return [] unless has_foreign_key?
  
  # Validate required configuration
  target_model = @foreign_key_config[:model]
  return [] unless target_model
  
  # Get records using scope if provided, otherwise all records
  records = if @foreign_key_config[:scope]
    @foreign_key_config[:scope].call
  else
    target_model.all
  end
  
  # Format options for select dropdown
  options = records.map do |record|
    display_value = case @foreign_key_config[:display]
    when Symbol
      record.respond_to?(@foreign_key_config[:display]) ? 
        record.public_send(@foreign_key_config[:display]) : 
        record.to_s
    when Proc
      @foreign_key_config[:display].call(record)
    else
      record.to_s
    end
    
    [display_value, record.id]
  end
  
  options
rescue => e
  # Graceful error handling
  if Rails.env.development?
    [["Error: #{e.message}", ""]]
  else
    []
  end
end

#habtm(**config) ⇒ Object



92
93
94
95
96
# File 'lib/elaine_crud/field_configuration.rb', line 92

def habtm(**config)
  return @habtm_config if config.empty?
  @habtm_config ||= {}
  @habtm_config.merge!(config)
end

#has_custom_display?Boolean

Returns:

  • (Boolean)


150
151
152
# File 'lib/elaine_crud/field_configuration.rb', line 150

def has_custom_display?
  @display_callback.present?
end

#has_custom_edit?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'lib/elaine_crud/field_configuration.rb', line 154

def has_custom_edit?
  @edit_callback.present?
end

#has_edit_partial?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'lib/elaine_crud/field_configuration.rb', line 158

def has_edit_partial?
  @edit_partial.present?
end

#has_foreign_key?Boolean

Returns:

  • (Boolean)


134
135
136
# File 'lib/elaine_crud/field_configuration.rb', line 134

def has_foreign_key?
  @foreign_key_config.present?
end

#has_habtm?Boolean

Returns:

  • (Boolean)


146
147
148
# File 'lib/elaine_crud/field_configuration.rb', line 146

def has_habtm?
  @habtm_config.present?
end

#has_has_many?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'lib/elaine_crud/field_configuration.rb', line 138

def has_has_many?
  @has_many_config.present?
end

#has_has_one?Boolean

Returns:

  • (Boolean)


142
143
144
# File 'lib/elaine_crud/field_configuration.rb', line 142

def has_has_one?
  @has_one_config.present?
end

#has_many(**config) ⇒ Object



82
83
84
85
# File 'lib/elaine_crud/field_configuration.rb', line 82

def has_many(**config)
  return @has_many_config if config.empty?
  @has_many_config = config
end

Get options for has_many relationship display



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/elaine_crud/field_configuration.rb', line 338

def has_many_related_records(controller_instance)
  return [] unless has_has_many?
  
  model_class = @has_many_config[:model]
  foreign_key = @has_many_config[:foreign_key]
  
  # This would be called from a parent record context
  parent_record = controller_instance.instance_variable_get(:@record)
  return [] unless parent_record
  
  related_records = parent_record.public_send(@field_name)
  
  # Apply scope if specified
  if @has_many_config[:scope]
    related_records = related_records.instance_eval(&@has_many_config[:scope])
  end
  
  related_records
end

#has_nested_create?Boolean

Returns:

  • (Boolean)


162
163
164
# File 'lib/elaine_crud/field_configuration.rb', line 162

def has_nested_create?
  @nested_create_config.present?
end

#has_one(**config) ⇒ Object



87
88
89
90
# File 'lib/elaine_crud/field_configuration.rb', line 87

def has_one(**config)
  return @has_one_config if config.empty?
  @has_one_config = config
end

#has_options?Boolean

Helper methods for checking configuration state

Returns:

  • (Boolean)


130
131
132
# File 'lib/elaine_crud/field_configuration.rb', line 130

def has_options?
  @options.present?
end

#nested_create(config = nil) ⇒ Object



123
124
125
126
127
# File 'lib/elaine_crud/field_configuration.rb', line 123

def nested_create(config = nil)
  return @nested_create_config if config.nil?
  # Allow nested_create true as shorthand - use true as the value since {} is not .present?
  @nested_create_config = config == true ? true : config
end

#render_display_value(record, controller_instance) ⇒ Object

Method to execute display callback



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/elaine_crud/field_configuration.rb', line 167

def render_display_value(record, controller_instance)
  # For virtual fields (those that don't exist on the model), use nil as field_value
  field_value = if record.respond_to?(@field_name)
                 record.public_send(@field_name)
               else
                 nil
               end
  
  case @display_callback
  when Symbol
    # Call method on controller instance
    if controller_instance.respond_to?(@display_callback, true)
      result = controller_instance.send(@display_callback, field_value, record)
    else
      raise NoMethodError, "Display callback method '#{@display_callback}' not found on #{controller_instance.class.name}"
    end
  when Proc
    # Call proc with field value and record
    # Execute the lambda with the controller that has access to helpers
    result = controller_instance.instance_exec(field_value, record, &@display_callback)
  else
    raise ArgumentError, "Display callback must be a Symbol or Proc, got #{@display_callback.class.name}"
  end
  
  # Ensure result is HTML safe
  result.respond_to?(:html_safe) ? result.html_safe : result.to_s.html_safe
rescue => e
  # Graceful error handling - show the error in development but fallback in production
  if Rails.env.development?
    %Q{<span class="text-red-500 text-xs">Error: #{e.message}</span>}.html_safe
  else
    field_value.to_s.html_safe
  end
end

#render_edit_field(record, controller_instance, form_builder = nil, view_context = nil) ⇒ Object

Method to execute edit callback



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/elaine_crud/field_configuration.rb', line 203

def render_edit_field(record, controller_instance, form_builder = nil, view_context = nil)
  field_value = record.public_send(@field_name)
  
  case @edit_callback
  when Symbol
    # Call method on controller instance
    if controller_instance.respond_to?(@edit_callback, true)
      result = controller_instance.send(@edit_callback, field_value, record, form_builder)
    else
      raise NoMethodError, "Edit callback method '#{@edit_callback}' not found on #{controller_instance.class.name}"
    end
  when Proc
    # Call proc with field value, record, and form builder
    if view_context
      # Execute the lambda in the view context so it has access to helpers
      result = view_context.instance_exec(field_value, record, form_builder, &@edit_callback)
    else
      # Fallback to direct call for backward compatibility
      result = @edit_callback.call(field_value, record, form_builder)
    end
  else
    raise ArgumentError, "Edit callback must be a Symbol or Proc, got #{@edit_callback.class.name}"
  end
  
  # Ensure result is HTML safe
  result.respond_to?(:html_safe) ? result.html_safe : result.to_s.html_safe
rescue => e
  # Graceful error handling - show the error in development but fallback in production
  if Rails.env.development?
    if view_context
      view_context.(:span, "Error: #{e.message}", class: "text-red-500 text-xs")
    else
      %Q{<span class="text-red-500 text-xs">Error: #{e.message}</span>}.html_safe
    end
  else
    # Fallback to basic text field
    if form_builder
      field_class = "block w-full border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm"
      form_builder.text_field(@field_name, class: field_class)
    else
      field_value.to_s.html_safe
    end
  end
end

#render_has_many_display(record, controller_instance) ⇒ Object

Get display value for has_many relationship



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/elaine_crud/field_configuration.rb', line 359

def render_has_many_display(record, controller_instance)
  return "" unless has_has_many?

  related_records = record.public_send(@field_name)

  if @has_many_config[:show_count]
    count = related_records.count

    if @has_many_config[:max_preview_items] && @has_many_config[:max_preview_items] > 0
      preview_records = related_records.limit(@has_many_config[:max_preview_items])
      display_field = @has_many_config[:display] || :id

      preview_text = preview_records.map do |rel_record|
        begin
          if display_field.is_a?(Proc)
            display_field.call(rel_record)
          else
            result = rel_record.public_send(display_field)
            result || "N/A"
          end
        rescue => e
          Rails.logger.error "ElaineCrud: Error calling #{display_field} on #{rel_record.class.name}##{rel_record.id}: #{e.message}"
          "Error"
        end
      end.join(", ")

      "#{count} items#{count > 0 ? ": #{preview_text}" : ""}"
    else
      "#{count} items"
    end
  else
    related_records.count.to_s
  end
rescue => e
  Rails.logger.error "ElaineCrud: Error rendering has_many display: #{e.message}"
  "Error loading relationships"
end

#render_has_one_display(record, controller_instance) ⇒ Object

Get display value for has_one relationship



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/elaine_crud/field_configuration.rb', line 398

def render_has_one_display(record, controller_instance)
  return "" unless has_has_one?

  related_record = record.public_send(@field_name)
  return "" if related_record.nil?

  display_field = @has_one_config[:display] || :id

  begin
    if display_field.is_a?(Proc)
      display_field.call(related_record)
    else
      result = related_record.public_send(display_field)
      result || "N/A"
    end
  rescue => e
    Rails.logger.error "ElaineCrud: Error calling #{display_field} on #{related_record.class.name}##{related_record.id}: #{e.message}"
    "Error"
  end
rescue => e
  Rails.logger.error "ElaineCrud: Error rendering has_one display: #{e.message}"
  "Error loading relationship"
end

#resolve_default_value(controller_instance) ⇒ Object

Method to get default value for new records



290
291
292
293
294
295
296
297
298
# File 'lib/elaine_crud/field_configuration.rb', line 290

def resolve_default_value(controller_instance)
  # TODO: Implement default value resolution
  # 1. Return nil if @default_value is nil
  # 2. If @default_value is a Proc or Lambda, call it with controller_instance as context
  # 3. If @default_value is any other value, return it directly
  # 4. Handle edge cases and ensure returned value is appropriate for the field type
  return @default_value if @default_value.present? && !@default_value.respond_to?(:call)
  return nil # Placeholder for callable defaults
end

#show_in_form?Boolean

Helper method to determine if field should be shown in forms

Returns:

  • (Boolean)


309
310
311
312
313
314
# File 'lib/elaine_crud/field_configuration.rb', line 309

def show_in_form?
  # TODO: Implement form visibility logic  
  # Default: show all permitted params except readonly fields in edit mode
  # Could be configurable in future: @show_in_form boolean
  !@readonly
end

#show_in_index?Boolean

Helper method to determine if field should be shown in index

Returns:

  • (Boolean)


301
302
303
304
305
306
# File 'lib/elaine_crud/field_configuration.rb', line 301

def show_in_index?
  # TODO: Implement index visibility logic
  # Default: show all fields except id and timestamps
  # Could be configurable in future: @show_in_index boolean
  !%w[id created_at updated_at].include?(@field_name.to_s)
end