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)


44
45
46
47
48
49
50
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
# File 'lib/madmin/resource.rb', line 44

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



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

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

.collection_action(&block) ⇒ Object



146
147
148
# File 'lib/madmin/resource.rb', line 146

def collection_action(&block)
  collection_actions << block
end

.collection_member_actionsObject

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



142
143
144
# File 'lib/madmin/resource.rb', line 142

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

.display_name(record) ⇒ Object



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

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

.edit_path(record) ⇒ Object



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

def edit_path(record)
  url_helpers.polymorphic_path([:madmin, route_namespace, becomes(record)], action: :edit)
end

.field_for_type(type) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
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
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
# File 'lib/madmin/resource.rb', line 150

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)


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

def friendly_model?
  model.respond_to? :friendly
end

.friendly_nameObject

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



79
80
81
# File 'lib/madmin/resource.rb', line 79

def friendly_name
  model_name.split("::").map { |part| part.underscore.humanize }.join(" / ").titlecase
end

.get_attribute(name) ⇒ Object



40
41
42
# File 'lib/madmin/resource.rb', line 40

def get_attribute(name)
  attributes[name]
end

.index_path(options = {}) ⇒ Object



93
94
95
# File 'lib/madmin/resource.rb', line 93

def index_path(options = {})
  url_helpers.polymorphic_path([:madmin, route_namespace, model], options)
end

.infer_type(name) ⇒ Object



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
# File 'lib/madmin/resource.rb', line 215

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



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

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


265
266
267
# File 'lib/madmin/resource.rb', line 265

def menu(options)
  @menu_options = options
end


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

def menu_options
  return false if @menu_options == false
  @menu_options ||= {}
  @menu_options.with_defaults(label: friendly_name.pluralize, 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_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_nameObject



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

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

.model_store_accessorsObject



260
261
262
263
# File 'lib/madmin/resource.rb', line 260

def model_store_accessors
  store_accessors = model.stored_attributes.values
  store_accessors.flatten
end

.new_pathObject



97
98
99
# File 'lib/madmin/resource.rb', line 97

def new_path
  url_helpers.polymorphic_path([:madmin, route_namespace, model], action: :new)
end

.param_keyObject



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

def param_key
  model.model_name.param_key
end

.permitted_paramsObject



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

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

.route_namespaceObject

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



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

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

.searchable_attributesObject



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

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

.show_path(record) ⇒ Object



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

def show_path(record)
  url_helpers.polymorphic_path([:madmin, route_namespace, becomes(record)])
end

.sortable_columnsObject



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

def sortable_columns
  model.column_names
end

.type_for_association(association) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
# File 'lib/madmin/resource.rb', line 244

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_helpersObject



256
257
258
# File 'lib/madmin/resource.rb', line 256

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