Class: Madmin::Resource

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

Constant Summary collapse

Attribute =
Data.define(:name, :type, :field)

Class Method Summary collapse

Class Method Details

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

Yields:

  • (config)


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

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
  if config.has_key?(:form)
    config.new = config[:form]
    config.edit = config[:form]
  end

  # New/create and edit/update need to match
  config.create = config[:create] if config.has_key?(:new)
  config.update = config[:update] if config.has_key?(:edit)

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

.becomes(record) ⇒ Object



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

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

.display_name(record) ⇒ Object



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

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

.edit_path(record) ⇒ Object



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

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

.field_for_type(type) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
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
# File 'lib/madmin/resource.rb', line 138

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)


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

def friendly_model?
  model.respond_to? :friendly
end

.friendly_nameObject

Returns singular name For example: “Forum::Post” -> “Forum / Post”



76
77
78
# File 'lib/madmin/resource.rb', line 76

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

.get_attribute(name) ⇒ Object



38
39
40
# File 'lib/madmin/resource.rb', line 38

def get_attribute(name)
  attributes[name]
end

.index_path(options = {}) ⇒ Object



90
91
92
# File 'lib/madmin/resource.rb', line 90

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

.infer_type(name) ⇒ Object



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

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



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

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

.member_action(&block) ⇒ Object



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

def member_action(&block)
  member_actions << block
end


253
254
255
# File 'lib/madmin/resource.rb', line 253

def menu(options)
  @menu_options = options
end


257
258
259
260
261
# File 'lib/madmin/resource.rb', line 257

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



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

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

.model_find(id) ⇒ Object



26
27
28
# File 'lib/madmin/resource.rb', line 26

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

.model_nameObject



30
31
32
# File 'lib/madmin/resource.rb', line 30

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

.model_store_accessorsObject



248
249
250
251
# File 'lib/madmin/resource.rb', line 248

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

.new_pathObject



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

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

.param_keyObject



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

def param_key
  model.model_name.param_key
end

.permitted_paramsObject



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

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



82
83
84
85
86
87
88
# File 'lib/madmin/resource.rb', line 82

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.singularize.underscore.to_sym : nil)
end

.scope(name) ⇒ Object



34
35
36
# File 'lib/madmin/resource.rb', line 34

def scope(name)
  scopes << name
end

.searchable_attributesObject



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

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

.show_path(record) ⇒ Object



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

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

.sortable_columnsObject



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

def sortable_columns
  model.column_names
end

.type_for_association(association) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
# File 'lib/madmin/resource.rb', line 232

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



244
245
246
# File 'lib/madmin/resource.rb', line 244

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