Module: RearSetup

Includes:
RearConstants
Defined in:
lib/rear/setup.rb,
lib/rear/setup/menu.rb,
lib/rear/setup/columns.rb,
lib/rear/setup/filters.rb,
lib/rear/setup/generic.rb,
lib/rear/setup/associations.rb

Constant Summary

Constants included from RearConstants

RearConstants::ASSETS__PATH, RearConstants::ASSETS__SUFFIX, RearConstants::ASSETS__SUFFIX_REGEXP, RearConstants::ASSOCS__STRUCT, RearConstants::COLUMNS__BOOLEAN_MAP, RearConstants::COLUMNS__DEFAULT_TYPE, RearConstants::COLUMNS__HANDLED_TYPES, RearConstants::COLUMNS__PANE_MAX_LENGTH, RearConstants::FILTERS__DECORATIVE_CMP, RearConstants::FILTERS__DEFAULT_TYPE, RearConstants::FILTERS__HANDLED_TYPES, RearConstants::FILTERS__QUERY_MAP, RearConstants::FILTERS__STR_TO_BOOLEAN, RearConstants::PAGER__SIDE_PAGES, RearConstants::PATH__TEMPLATES

Instance Method Summary collapse

Instance Method Details

#assoc_columns(*columns) ⇒ Object

when rendering some model in a “remote” association pane, all columns of current model will be displayed.

‘assoc_columns` allow to set a list of “remotely” displayed columns.



28
29
30
31
# File 'lib/rear/setup/associations.rb', line 28

def assoc_columns *columns
  @__rear__assoc_columns = columns if columns.any?
  @__rear__assoc_columns
end

#decorative_filter(*args, &proc) ⇒ Object

Note:

decorative filters will not actually query the db, so you can name them as you want.

Note:

decorative filters does not support custom comparison functions

sometimes you need to filter by some value that has too much options. For ex. you want to filter pages by author and there are about 1000 authors in db. displaying all authors within a single dropdown filter is kinda cumbersome. we need to somehow narrow them down. decorative filters allow to do this with easy. in our case, we do not display the authors until a letter selected.

Examples:

class Pages < E
  include Rear
  model PageModel

  decorative_filter :letter, :select do
    ('A'..'Z').to_a
  end

  filter :author_id, :select do
    if letter = filter?(:letter) # use here the name of decorative filter
      authors = {}
      AuthorModel.all(:name.like => "%#{letter}%").each |a|
        authors[a.id] = a.name
      end
      authors
    else
      {"" => "Select a letter please"}
    end
  end
end


200
201
202
203
204
# File 'lib/rear/setup/filters.rb', line 200

def decorative_filter *args, &proc
  html_attrs = args.last.is_a?(Hash) ? Hash[args.pop] : {}
  setup = {decorative?: true, cmp: FILTERS__DECORATIVE_CMP}
  filter *args << html_attrs.merge(setup), &proc
end

#editor_attrs(attrs = {}) ⇒ Object

Note:

will override any attrs set via ‘html_attrs`

set HTML attributes to be used on all columns only on editor pages.



105
106
107
108
# File 'lib/rear/setup/columns.rb', line 105

def editor_attrs attrs = {}
  @__rear__editor_attrs = attrs if attrs.any? && @__rear__editor_attrs.nil?
  @__rear__editor_attrs || html_attrs
end

#filter(column, type = nil, opts_and_or_html_attrs = {}, &proc) ⇒ Object

Note:

comparison function :text/:string filters will use :like comparison function by default: … WHERE name LIKE ‘%VALUE%’ …

:checkbox filters will use :in comparison function by default: … WHERE column IN (‘VALUE1’, ‘VALUE2’) … if you use a custom cmp function with a :checkbox filter, filter’s column will be compared to each selected value: … WHERE (column LIKE ‘%VALUE1%’ OR column LIKE ‘%VALUE2%’) …

any other types will use :eql comparison function by default: “… WHERE created_at = ‘VALUE’ …

to use a non-default comparison function, set it via :cmp option: ‘filter :name, :cmp => :eql`

available comparison functions:

- :eql       # equal
- :not       # not equal
- :gt        # greater than
- :gte       # greater than or equal
- :lt        # less than
- :lte       # less than or equal
- :like      # - column LIKE '%VALUE%'
- :unlike    # - column NOT LIKE '%VALUE%'
- :_like     # match beginning of line - column LIKE 'VALUE%'
- :_unlike   # - column NOT LIKE 'VALUE%'
- :like_     # match end of line - column LIKE '%VALUE'
- :unlike_   # - column NOT LIKE '%VALUE'
- :_like_    # exact match - column LIKE 'VALUE'
- :_unlike_  # - column NOT LIKE 'VALUE'
Note:

if type not given, Rear will use the type of the column with same name, if any. if no column found, it will use :text

Note:

:radio, :checkbox and :select filters requires a block to run. block should return an Array or a Hash. use an Array when stored keys are the same as displayed values. use a Hash when stored keys are different. Important! if no block given, Rear will search for a column with same name and type and inherit options from there. so if you have say a :checkbox column named :colors with defined options, you only need to do ‘filter :colors`, without specifying type and options. type and options will be inherited from earlier defined column.

add a filter.

by default a text filter will be rendered. to define filters of another type, pass desired type as a Symbol via second argument.

acceptable types:

- :string/:text
- :select
- :radio
- :checkbox
- :date
- :datetime
- :time
- :boolean

Examples:


class Page < ActiveRecord::Base
  # ...
  include Rear
  rear do

    # text filter using :like comparison function
    filter :name

    # text filter using :eql comparison function
    filter :name, :cmp => :eql

    # date filter using :eql comparison function
    filter :created_at, :date

    # date filter using :gte comparison function
    filter :created_at, :date, :cmp => :gte

    # dropdown filter using :eql comparison function
    filter :color, :select do
      ['Red', 'Green', 'Blue']
    end

    # dropdown filter using :like comparison function
    filter :color, :select, :cmp => :like do
      ['Red', 'Green', 'Blue']
    end
  end
end

:radio filter using Hash


rear do
  filter :color, :radio do
    {'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'}
  end
end

inheriting type and options from a earlier defined column


rear do
  column :colors, :checkbox do
    options 'Red', 'Green', 'Blue'
  end

  filter :colors # type and options inherited from :colors column
end

Parameters:

  • column (Symbol)
  • type (Symbol) (defaults to: nil)
  • opts_and_or_html_attrs (Hash) (defaults to: {})
  • options (Proc)

    block used on :select, :radio and :checkbox filters should return Array or Hash.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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
# File 'lib/rear/setup/filters.rb', line 121

def filter column, type = nil, opts_and_or_html_attrs = {}, &proc
  
  opts = (opts_and_or_html_attrs||{}).dup
  type.is_a?(Hash) && (opts = type.dup) && (type = nil) && (opts_and_or_html_attrs = nil)
  matching_column = columns.find {|c| c && c.first == column}
  
  # if no type given, inheriting it from a column with same name, if any.
  type ||= (matching_column||[])[1]
  type = FILTERS__DEFAULT_TYPE unless FILTERS__HANDLED_TYPES.include?(type)

  # if filter is of :select, :radio or :checkbox type and no options block given,
  # inheriting it from a column with same name, if any.
  if proc.nil? && matching_column && matching_column[1] == type
    mci = RearInput.new(matching_column[0], type, &matching_column[3])
    mci.optioned? && proc = lambda { mci.options }
  end

  # using defaults if no comparison function given
  unless cmp = opts.delete(:cmp)
    cmp = case type
    when :text, :string
      :like
    when :checkbox
      :in
    else
      :eql
    end
  end

  unless label = opts.delete(:label)
    label = column.to_s
    label << '?' if type == :boolean
  end

  (filters[column.to_sym] ||= {})[cmp] = {
       template: 'filters/%s.slim' % type,
           type: type,
          label: label.freeze,
    decorative?: opts.delete(:decorative?),
          attrs: opts.freeze,
           proc: proc
  }.freeze
end

#html_attrs(attrs = {}) ⇒ Object

set HTML attributes to be used on all columns on both pane and editor pages



91
92
93
94
# File 'lib/rear/setup/columns.rb', line 91

def html_attrs attrs = {}
  @__rear__html_attrs = attrs if attrs.any? && @__rear__html_attrs.nil?
  @__rear__html_attrs || {}
end

#ignored_assocs(*assocs) ⇒ Object Also known as: ignored_assoc, ignore_assocs, ignore_assoc

ignore some assocs. this is a cosmetic measure - assocs just wont be displayed on frontend but the API for their manipulation will still work



15
16
17
18
# File 'lib/rear/setup/associations.rb', line 15

def ignored_assocs *assocs
  (@__rear__ignored_assocs ||= []).concat(assocs) if assocs.any?
  (@__rear__ignored_assocs ||  [])
end

#input(name, type = nil, opts_and_or_html_attrs = {}, &proc) ⇒ Object

add new column or override automatically added one

Examples:

input :name
# => <input type="text" value="...">
input :name, :style => "width: 100%;"
# => <input style="width: 100%;" type="text" ...>
input :name, :text, :cols => 40
# => <textarea cols="40" ...>...</textarea>

display author name only on pane pages

input(:author_id) { disable :editor }

Ace Editor

input :content, :ace

CKEditor

input :content, :ckeditor

Parameters:

  • name (Symbol)
  • type (Symbol) (defaults to: nil)

    one of [:string, :text, :date, :time, :datetime, :boolean] default: :string

  • opts_and_or_html_attrs (Hash) (defaults to: {})

Options Hash (opts_and_or_html_attrs):

  • :pane (Object)

    when set to false the column wont be displayed on pane pages

  • :editor (Object)

    when set to false the column wont be displayed on editor pages

  • :label (Object)
  • :readonly (Object)
  • :disabled (Object)
  • :multiple (Object)
  • any (Object)

    attributes to be added to HTML tag



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/rear/setup/columns.rb', line 40

def input name, type = nil, opts_and_or_html_attrs = {}, &proc

  type.is_a?(Hash) && (opts_and_or_html_attrs = type) && (type = nil)
  opts_and_or_html_attrs[:row] = opts_and_or_html_attrs[:row] ?
    opts_and_or_html_attrs[:row].to_s : @__rear__row

  existing_column = nil
  columns.each_with_index {|c,i| c && c.first == name && existing_column = [c,i]}
  column = existing_column ? Array.new(existing_column.first) : []

  column[0] = name
  column[1] = type ? type.to_s.downcase.to_sym : column[1] || COLUMNS__DEFAULT_TYPE
  column[2] = (column[2]||{}).merge(opts_and_or_html_attrs).freeze
  column[3] = proc
  column.freeze

  existing_column ?
    columns[existing_column.last] = column :
    columns << column
end

#internal_filter(&proc) ⇒ Object

# then we using internal_filter

  # to yield selected category and filter articles
  internal_filter do
    if category_id = filter?(:Category)
      Article.all(category_id: category_id.to_i)
    end
  end
end


304
305
306
307
308
309
310
311
312
# File 'lib/rear/setup/filters.rb', line 304

def internal_filter &proc
  # instance_exec at runtime is expensive enough,
  # so compiling procs into methods at load time.
  chunks = [self.to_s, proc.__id__]
  name = ('__rear__%s__' % chunks.join('_').gsub(/\W/, '_')).to_sym
  define_method name, &proc
  private name
  internal_filters.push(name)
end

#items_per_page(n = nil) ⇒ Object Also known as: ipp



33
34
35
36
# File 'lib/rear/setup/generic.rb', line 33

def items_per_page n = nil
  @__rear__ipp = n.to_i if n
  @__rear__ipp || 10
end

put current controller under some group.

Examples:

put Articles and Pages under Cms dropdown

class Articles < E
  include Rear
  under :Cms
end
class Pages < E
  include Rear
  under :Cms
end


34
35
36
37
# File 'lib/rear/setup/menu.rb', line 34

def menu_group group = nil
  @__rear__menu_group = group.to_s if group
  @__rear__menu_group
end

by default all controllers are shown in main menu using the demodulized controller name.

to use a custom label, set it via ‘menu_label` or its alias - `label` to hide a controller from menu set label to false.



8
9
10
11
# File 'lib/rear/setup/menu.rb', line 8

def menu_label label = nil
  @__rear__menu_label = label.freeze if label || label == false
  @__rear__menu_label.nil? ? default_label : @__rear__menu_label
end

by default controllers will be shown in the menu in the order they was defined. to have a controller shown before other ones set its menu_position to a higher number.



16
17
18
19
# File 'lib/rear/setup/menu.rb', line 16

def menu_position position = nil
  @__rear__menu_position = position.to_i if position
  @__rear__menu_position || 0
end

#model(model = nil, opts = {}, &proc) ⇒ Object

tell controller to create a CRUD interface for given model opts and proc will be passed to Espresso’s ‘crudify` helper.

Parameters:

  • model (Class) (defaults to: nil)
  • opts (Hash) (defaults to: {})

    to be passed to ‘crudify` method

  • proc (Proc)

    to be passed to ‘crudify` method



10
11
12
13
14
15
16
17
18
# File 'lib/rear/setup/generic.rb', line 10

def model model = nil, opts = {}, &proc
  return @__rear__model if @__rear__model || model.nil?
  model = RearUtils.extract_constant(model)
  RearUtils.is_orm?(model) ||
    raise(ArgumentError, '"%s" is not a ActiveRecord/DataMapper/Sequel model' % model.inspect)
  @__rear__model = model
  @__rear__default_label = model.name.gsub(/\W/, '_').freeze
  RearControllerSetup.crudify self, model, opts, &proc
end

#on_delete(&proc) ⇒ Object Also known as: on_destroy



50
51
52
# File 'lib/rear/setup/generic.rb', line 50

def on_delete &proc
  before :destroy, &proc
end

#on_save(&proc) ⇒ Object

executed when new item created and when existing item updated



40
41
42
43
# File 'lib/rear/setup/generic.rb', line 40

def on_save &proc
  # const_get(:RearController).
  before :save, &proc
end

#on_update(&proc) ⇒ Object

executed when existing item updated



46
47
48
# File 'lib/rear/setup/generic.rb', line 46

def on_update &proc
  before :update, &proc
end

#order_by(*columns) ⇒ Object



28
29
30
31
# File 'lib/rear/setup/generic.rb', line 28

def order_by *columns
  @__rear__order = columns if columns.any?
  @__rear__order
end

#pane_attrs(attrs = {}) ⇒ Object

Note:

will override any attrs set via ‘html_attrs`

set HTML attributes to be used on all columns only on pane pages.



98
99
100
101
# File 'lib/rear/setup/columns.rb', line 98

def pane_attrs attrs = {}
  @__rear__pane_attrs = attrs if attrs.any? && @__rear__pane_attrs.nil?
  @__rear__pane_attrs || html_attrs
end

#pkey(key = nil) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/rear/setup/generic.rb', line 20

def pkey key = nil
  return unless model
  @__rear__pkey = key if key
  @__rear__pkey ||
    raise(ArgumentError, "Was unable to automatically detect primary key for %s model.
      Please set it manually via `pkey key_name`" % model)
end

#quick_filter(column, *args) ⇒ Object

Examples:

Array with default comparison function

quick_filter :color, 'Red', 'Green', 'Blue'
... WHERE color = '[Red|Green|Blue]'

Array with custom comparison function

quick_filter :color, 'Red', 'Green', 'Blue', :cmp => :like
... WHERE color LIKE '[Red|Green|Blue]'

Hash with default comparison function

quick_filter :color, 'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'
... WHERE color = '[r|g|b]'

Hash with custom comparison function

quick_filter :color, :cmp => :like, 'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'
... WHERE color LIKE '%[r|g|b]%'

Hash with comparison function defined per filter

quick_filter :color, [:like, 'r'] => 'Red', 'g' => 'Green', 'b' => 'Blue'
on Red
... WHERE color LIKE '%r%'
on Green or Blue
... WHERE color = '[g|b]'


229
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
# File 'lib/rear/setup/filters.rb', line 229

def quick_filter column, *args
  
  options = args.last.is_a?(Hash) ? args.pop : {}
  cmp = options.delete(:cmp) || :eql
  query_formats = FILTERS__QUERY_MAP.call(orm)
  if query_format = query_formats[cmp]
    options = Hash[options.map do |k,v|
      [
        v.to_s,
        k.is_a?(Array) ? [query_formats[k.first], k.last] : [query_format, k]
      ]
    end]

    # if options provided as arguments, adding them to options Hash
    args.each {|a| options[a.to_s] = [query_format, a.to_s] }

    # if no options given,
    # inheriting them from a column with same name, if any.
    if options.empty? && mc = columns.find {|c| c && c.first == column}
      mci = RearInput.new(mc[0], mc[1], &mc[3])
      mci.optioned? && mci.options.each_pair do |k,v|
        options[v.to_s] = [query_format, k]
      end
    end

    quick_filters[column.to_sym] = options
  end
end

#readonly!Object



55
56
57
# File 'lib/rear/setup/generic.rb', line 55

def readonly!
  @__rear__readonly = true
end

#readonly_assocs(*assocs) ⇒ Object Also known as: readonly_assoc

make some assocs readonly. this is a cosmetic measure - frontend just wont let user modify them but the API for their manipulation will still work



6
7
8
9
# File 'lib/rear/setup/associations.rb', line 6

def readonly_assocs *assocs
  (@__rear__readonly_assocs ||= []).concat(assocs) if assocs.any?
  (@__rear__readonly_assocs ||  [])
end

#reset_columns!Object

reset any automatically(or manually) added columns



62
63
64
# File 'lib/rear/setup/columns.rb', line 62

def reset_columns!
  @__rear__columns = {}
end

#row(label = nil, &proc) ⇒ Object

display multiple columns in a row(on editor)

Examples:

using a block


row :Location do
  column :country
  column :state
  column :city
end

without a block


column :country, :row => :Location
column :state,   :row => :Location
column :city,    :row => :Location


82
83
84
85
86
87
88
# File 'lib/rear/setup/columns.rb', line 82

def row label = nil, &proc
  # explicit labels will be strings and implicit ones will be numbers
  # as a way to distinguish them when rendering templates
  @__rear__row = label ? label.to_s : (Time.now.to_f + rand)
  self.instance_exec(&proc) if proc
  @__rear__row = nil
end