Class: Madmin::Resource

Inherits:
Object
  • Object
show all
Defined in:
lib/madmin/resource.rb

Defined Under Namespace

Classes: Attribute

Class Method Summary collapse

Class Method Details

.attribute(name, type = nil, **options) {|config| ... } ⇒ Object

Yields:

  • (config)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/madmin/resource.rb', line 51

def attribute(name, type = nil, **options)
  type ||= infer_type(name)
  field = options.delete(:field) || field_for_type(type)

  if field.nil?
    Rails.logger.warn <<~MESSAGE
      WARNING: Madmin could not infer a field type for `#{name}` attribute in `#{self.name}`. Defaulting to a String type.
      #{caller.find { _1.start_with? Rails.root.to_s }}
    MESSAGE
    field = Fields::String
  end

  config = ActiveSupport::OrderedOptions.new.merge(options)
  yield config if block_given?

  # Form is an alias for new & edit but shouldn't override their values if explicitly set (true/false)
  config.new = config[:form] if config[:new].nil?
  config.edit = config[:form] if config[:edit].nil?

  # New/create and edit/update need to match
  config.create = config.new
  config.update = config.edit

  # Remove any nil values to use the defaults
  config.compact!

  attributes[name] = Attribute.new(
    name: name,
    type: type,
    field: field.new(attribute_name: name, model: model, resource: self, options: config)
  )
end

.becomes(record) ⇒ Object



116
117
118
# File 'lib/madmin/resource.rb', line 116

def becomes(record)
  record.instance_of?(model) ? record : record.becomes(model)
end

.collection_action(&block) ⇒ Object



161
162
163
# File 'lib/madmin/resource.rb', line 161

def collection_action(&block)
  collection_actions << block
end

.collection_member_actions ⇒ Object

Member actions that should also render in each row on the index page



157
158
159
# File 'lib/madmin/resource.rb', line 157

def collection_member_actions
  member_actions.select { |action| action.respond_to?(:collection?) && action.collection? }
end

.display_name(record) ⇒ Object



128
129
130
# File 'lib/madmin/resource.rb', line 128

def display_name(record)
  "#{record.model_name.human} ##{record.id}"
end

.edit_path(record, options = nil) ⇒ Object



112
113
114
# File 'lib/madmin/resource.rb', line 112

def edit_path(record, options = nil)
  build_polymorphic_path([:madmin, route_namespace, becomes(record)], options, action: :edit)
end

.field_for_type(type) ⇒ Object



165
166
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
201
202
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
# File 'lib/madmin/resource.rb', line 165

def field_for_type(type)
  {
    binary: Fields::String,
    blob: Fields::Text,
    boolean: Fields::Boolean,
    currency: Fields::Currency,
    date: Fields::Date,
    datetime: Fields::DateTime,
    decimal: Fields::Decimal,
    enum: Fields::Enum,
    float: Fields::Float,
    hstore: Fields::Json,
    integer: Fields::Integer,
    json: Fields::Json,
    jsonb: Fields::Json,
    primary_key: Fields::String,
    select: Fields::Select,
    string: Fields::String,
    text: Fields::Text,
    time: Fields::Time,
    timestamp: Fields::Time,
    timestamptz: Fields::Time,
    password: Fields::Password,
    file: Fields::File,

    # Postgres specific types
    bit: Fields::String,
    bit_varying: Fields::String,
    box: Fields::String,
    cidr: Fields::String,
    circle: Fields::String,
    citext: Fields::Text,
    daterange: Fields::String,
    inet: Fields::String,
    int4range: Fields::String,
    int8range: Fields::String,
    interval: Fields::String,
    line: Fields::String,
    lseg: Fields::String,
    ltree: Fields::String,
    macaddr: Fields::String,
    money: Fields::String,
    numrange: Fields::String,
    oid: Fields::String,
    path: Fields::String,
    point: Fields::String,
    polygon: Fields::String,
    tsrange: Fields::String,
    tstzrange: Fields::String,
    tsvector: Fields::String,
    uuid: Fields::String,
    xml: Fields::Text,

    # Associations
    attachment: Fields::Attachment,
    attachments: Fields::Attachments,
    belongs_to: Fields::BelongsTo,
    polymorphic: Fields::Polymorphic,
    has_many: Fields::HasMany,
    has_one: Fields::HasOne,
    rich_text: Fields::RichText,
    nested_has_many: Fields::NestedHasMany
  }[type]
end

.friendly_model? ⇒ Boolean

Returns:

  • (Boolean)


132
133
134
# File 'lib/madmin/resource.rb', line 132

def friendly_model?
  model.respond_to? :friendly
end

.friendly_name(count: 1) ⇒ Object

Returns singular name For example: "Forum::Post" -> "Post"



86
87
88
# File 'lib/madmin/resource.rb', line 86

def friendly_name(count: 1)
  model.model_name.human(count: count, default: model_name.titleize.pluralize(count, I18n.locale))
end

.get_attribute(name) ⇒ Object



47
48
49
# File 'lib/madmin/resource.rb', line 47

def get_attribute(name)
  attributes[name]
end

.index_path(options = nil) ⇒ Object



100
101
102
# File 'lib/madmin/resource.rb', line 100

def index_path(options = nil)
  build_polymorphic_path([:madmin, route_namespace, model], options)
end

.infer_type(name) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/madmin/resource.rb', line 230

def infer_type(name)
  name_string = name.to_s

  if model.attribute_types.include?(name_string)
    column_type = model.attribute_types[name_string]
    if column_type.is_a? ::ActiveRecord::Enum::EnumType
      :enum
    else
      column_type.type || :string
    end
  elsif (association = model.reflect_on_association(name))
    type_for_association(association)
  elsif model.reflect_on_association(:"rich_text_#{name_string}")
    :rich_text
  elsif model.reflect_on_association(:"#{name_string}_attachment")
    :attachment
  elsif model.reflect_on_association(:"#{name_string}_attachments")
    :attachments

  # has_secure_password
  elsif model.attribute_types.include?("#{name_string}_digest") || name_string.ends_with?("_confirmation")
    :password

    # ActiveRecord Store
  elsif model_store_accessors.include?(name)
    :string
  end
end

.inherited(base) ⇒ Object



12
13
14
15
16
17
18
# File 'lib/madmin/resource.rb', line 12

def inherited(base)
  base.attributes = attributes.dup
  base.member_actions = member_actions.dup
  base.collection_actions = collection_actions.dup
  base.scopes = scopes.dup
  super
end

.member_action(collection: false, &block) ⇒ Object



152
153
154
# File 'lib/madmin/resource.rb', line 152

def member_action(collection: false, &block)
  member_actions << MemberAction.new(collection: collection, &block)
end


282
283
284
# File 'lib/madmin/resource.rb', line 282

def menu(options)
  @menu_options = options
end


286
287
288
289
290
# File 'lib/madmin/resource.rb', line 286

def menu_options
  return false if @menu_options == false
  @menu_options ||= {}
  @menu_options.with_defaults(label: model.model_name.i18n_key, url: index_path)
end

.model(value = nil) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/madmin/resource.rb', line 20

def model(value = nil)
  if value
    @model = value
  else
    @model ||= model_name.constantize
  end
end

.model_column_names ⇒ Object



144
145
146
# File 'lib/madmin/resource.rb', line 144

def model_column_names
  model.column_names
end

.model_find(id) ⇒ Object



28
29
30
# File 'lib/madmin/resource.rb', line 28

def model_find(id)
  friendly_model? ? model.friendly.find(id) : model.find(id)
end

.model_name ⇒ Object



32
33
34
# File 'lib/madmin/resource.rb', line 32

def model_name
  to_s.chomp("Resource").classify
end

.model_store_accessors ⇒ Object



275
276
277
278
279
280
# File 'lib/madmin/resource.rb', line 275

def model_store_accessors
  return [] unless model.respond_to?(:stored_attributes)

  store_accessors = model.stored_attributes.values
  store_accessors.flatten
end

.new_path(options = nil) ⇒ Object



104
105
106
# File 'lib/madmin/resource.rb', line 104

def new_path(options = nil)
  build_polymorphic_path([:madmin, route_namespace, model], options, action: :new)
end

.param_key ⇒ Object



120
121
122
# File 'lib/madmin/resource.rb', line 120

def param_key
  model.model_name.param_key
end

.permitted_params ⇒ Object



124
125
126
# File 'lib/madmin/resource.rb', line 124

def permitted_params
  attributes.values.filter { |a| a.field.visible?(:form) }.map { |a| a.field.to_param }
end

.readonly? ⇒ Boolean

Returns:

  • (Boolean)


136
137
138
# File 'lib/madmin/resource.rb', line 136

def readonly?
  false
end

.route_namespace ⇒ Object

Support for isolated namespaces Finds parent module class to include in polymorphic urls



92
93
94
95
96
97
98
# File 'lib/madmin/resource.rb', line 92

def route_namespace
  return @route_namespace if instance_variable_defined?(:@route_namespace)
  namespace = model.module_parents.detect do |n|
    n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
  end
  @route_namespace = (namespace ? namespace.name.underscore.to_sym : nil)
end

.scope(name) ⇒ Object



36
37
38
# File 'lib/madmin/resource.rb', line 36

def scope(name)
  scopes << name
end

.scope_label(name) ⇒ Object

Label for a scope button on the index. Looks up madmin.scopes.<param_key>., then madmin.scopes. in I18n, falling back to the humanized scope name. Override for custom labels.



43
44
45
# File 'lib/madmin/resource.rb', line 43

def scope_label(name)
  I18n.t("madmin.scopes.#{param_key}.#{name}", default: [:"madmin.scopes.#{name}", name.to_s.humanize])
end

.searchable_attributes ⇒ Object



148
149
150
# File 'lib/madmin/resource.rb', line 148

def searchable_attributes
  attributes.values.select { |a| a.field.searchable? }
end

.show_path(record, options = nil) ⇒ Object



108
109
110
# File 'lib/madmin/resource.rb', line 108

def show_path(record, options = nil)
  build_polymorphic_path([:madmin, route_namespace, becomes(record)], options)
end

.sortable_columns ⇒ Object



140
141
142
# File 'lib/madmin/resource.rb', line 140

def sortable_columns
  model_column_names
end

.type_for_association(association) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
# File 'lib/madmin/resource.rb', line 259

def type_for_association(association)
  if association.has_one?
    :has_one
  elsif association.collection?
    :has_many
  elsif association.polymorphic?
    :polymorphic
  else
    :belongs_to
  end
end

.url_helpers ⇒ Object



271
272
273
# File 'lib/madmin/resource.rb', line 271

def url_helpers
  @url_helpers ||= Rails.application.routes.url_helpers
end