Class: ActionPresenter::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/action_presenter/base.rb,
lib/action_presenter/base/form.rb,
lib/action_presenter/base/html.rb,
lib/action_presenter/base/bootstrap.rb

Constant Summary collapse

PRESENTER_CLASSES =

inheritance tracking

[]
PRESENTER_ATTRIBUTES =
%i[ content record ]
BOOTSTRAP_SIZES =
%w[ xs sm md lg ]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBase

core methods



12
13
# File 'lib/action_presenter/base.rb', line 12

def initialize
end

Instance Attribute Details

#form_builderObject

Returns the value of attribute form_builder.



3
4
5
# File 'lib/action_presenter/base/form.rb', line 3

def form_builder
  @form_builder
end

#recordObject

Returns the value of attribute record.



17
18
19
# File 'lib/action_presenter/base.rb', line 17

def record
  @record
end

#viewObject

Returns the value of attribute view.



17
18
19
# File 'lib/action_presenter/base.rb', line 17

def view
  @view
end

Class Method Details

.define_extension(base_method, new_method, *extension_params) ⇒ Object



117
118
119
120
121
# File 'lib/action_presenter/base.rb', line 117

def define_extension(base_method, new_method, *extension_params)
  define_method(new_method) do |*params, &content_block|
    send(base_method, *params, *extension_params.deep_dup, &content_block)
  end
end

.generate_bootstrap_presenter_methods!Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/action_presenter/base/bootstrap.rb', line 4

def self.generate_bootstrap_presenter_methods!
  ["", "offset"].each do |namespace|
    define_method("col_#{namespace}_class".squeeze("_")) do |*hashes, &content_block|
      classes = []
      BOOTSTRAP_SIZES.each do |col_size|
        col_width = extract("col_#{col_size}", *hashes).to_i.nonzero?
        if col_width
          col_width += 1 if extract("#{col_size}_expand", *hashes) == true && col_width < 11
          col_width -= 1 if extract("#{col_size}_compact", *hashes) == true && col_width > 2
          raise ArgumentError, "col_width must be between 1 and 12" if !col_width.between?(1,12)
          # col-sm-3
          classes << "col-#{col_size}-#{namespace}-#{col_width}".squeeze("-")
        end
      end
      [extract_html(*hashes)[:class], *classes].join(" ")
    end
  end

  define_method(:col_div) do |*hashes, &content_block|
    classes = []
    BOOTSTRAP_SIZES.each do |col_size|
      col_width = extract("col_#{col_size}", *hashes).to_i.nonzero?
      col_offset_width = extract("col_#{col_size}_offset", *hashes).to_i.nonzero?
      if col_width
        col_width += 1 if extract("#{col_size}_expand", *hashes) == true && col_width < 11
        col_width -= 1 if extract("#{col_size}_compact", *hashes) == true && col_width > 2
        classes << col_class("col_#{col_size}" => col_width)
      end
      if col_offset_width
        col_offset_width += 1 if extract("#{col_size}_offset_expand", *hashes) == true && col_offset_width < 11
        col_offset_width -= 1 if extract("#{col_size}_offset_compact", *hashes) == true && col_offset_width > 2
        raise ArgumentError, "col_width must be between 1 and 12" if !col_offset_width.between?(1,12)
        classes << col_offset_class("col_#{col_size}" => col_offset_width)
      end
    end
    div_tag(*hashes, add_class: classes.join(" "), &content_block)
  end

  define_method(:check_box_div_label) do |*hashes, &content_block|
    content = extract_content(*hashes, &content_block)
    check_box_div(*hashes) { label_tag(content: content) }
  end

  define_method(:label_span) do |label, *hashes, &content_block|
    span_tag(*hashes, add_class: "label label-#{label}", &content_block)
  end

  define_method(:alert_div) do |alert, *hashes, &content_block|
    div_tag(*hashes, class: "alert alert-#{alert} alert-dismissible", role: "alert", &content_block)
  end

  define_method(:dismiss_button) do |target, *hashes, &content_block|
    button_tag(*hashes, "aria-label" => "Dismiss", data: { dismiss: target.to_s }, &content_block)
  end

  define_method(:dropdown_toggle_btn) do |*hashes, &content_block|
    btn = extract(:btn, *hashes, btn: "default")
    button_tag(*hashes, class: "btn btn-#{btn} dropdown-toggle", data: { toggle: "dropdown" }, &content_block)
  end

  define_method(:glyphicon) do |glyphicon, *hashes, &content_block|
    span_tag(*hashes, add_class: "glyphicon glyphicon-#{glyphicon}", &content_block)
  end

  # override core classes
  {
    core_label_class:     -> { "control-label" },
    core_select_class:    -> { "form-control" },
    core_field_class:     -> { "form-control" },
    core_text_area_class: -> { "form-control" },
  }.each do |method, block|
    define_method(method, &block)
  end

  # div
  define_extension(:div_tag, :row_div,         class: "row")
  define_extension(:div_tag, :form_group_div,  class: "form-group")
  define_extension(:div_tag, :dropdown_div,    class: "dropdown")
  define_extension(:div_tag, :check_box_div,   class: "checkbox")
  # span
  define_extension(:span_tag, :caret_span, class: "caret")
  # ul
  define_extension(:ul_tag, :dropdown_menu_ul, class: "dropdown-menu", role: "menu")
  # label
  define_extension(:label_tag, :radio_label,    class: "radio")
  define_extension(:label_tag, :check_box_label, class: "checkbox")
end

.inherited(new_presenter_class) ⇒ Object



6
7
8
9
# File 'lib/action_presenter/base.rb', line 6

def self.inherited(new_presenter_class)
  PRESENTER_CLASSES << new_presenter_class
  super
end

.presenter_optionsObject



123
124
125
126
127
128
# File 'lib/action_presenter/base.rb', line 123

def presenter_options
  @presenter_options ||= ActiveSupport::OrderedOptions.new.merge(
    verify_content: true,
    presents: name.underscore.remove(/_presenter\z/).singularize.to_sym,
  )
end

Instance Method Details

#button_tag(*hashes, &content_block) ⇒ Object



33
34
35
36
# File 'lib/action_presenter/base/html.rb', line 33

def button_tag(*hashes, &content_block)
  default_html = { type: "button" }
  html_tag(:button, *hashes, default_html, &content_block)
end

#check_box(name, *hashes) ⇒ Object



76
77
78
79
80
81
# File 'lib/action_presenter/base/form.rb', line 76

def check_box(name, *hashes)
  default_html = { class: check_box_class }
  checked_value   = extract(:checked_value, *hashes, checked_value: "true")
  unchecked_value = extract(:unchecked_value, *hashes, unchecked_value: "false")
  form_builder.check_box(name, extract_html(*hashes, default_html), checked_value, unchecked_value)
end

#check_box_tag(name, *hashes, &content_block) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/action_presenter/base/html.rb', line 92

def check_box_tag(name, *hashes, &content_block)
  default_html    = { id: name.to_s.slugify_html, name: name, class: check_box_class }
  checked_value   = extract_content(*hashes, content: "true").presence
  unchecked_value = extract(:unchecked_value, *hashes).presence
  checked         = extract(:checked, *hashes, checked: view.params.to_unsafe_h.dig_html(name).presence == checked_value).present?
  check_box_html  = extract_html(*hashes, default_html)
  content = "".html_safe
  if unchecked_value.present?
    content += view.check_box_tag(nil, unchecked_value, true, default_html.merge(id: nil, class: nil, style: "display: none;"))
  end
  content + view.check_box_tag(nil, checked_value, checked, check_box_html)
end

#date_select(name, *hashes) ⇒ Object



54
55
56
57
58
59
60
61
62
63
# File 'lib/action_presenter/base/form.rb', line 54

def date_select(name, *hashes)
  default_html = { class: date_select_class }
  hashes << { order: %i[ month day year ] }
  select_options = {}
  %i[
    include_blank prompt
    order default start_year end_year add_month_numbers use_short_month
  ].each { |key| select_options[key] = extract(key, *hashes) }
  form_builder.date_select(name, select_options.compact, extract_html(*hashes, default_html))
end

#datetime_select(name, *hashes) ⇒ Object



65
66
67
68
69
70
71
72
73
74
# File 'lib/action_presenter/base/form.rb', line 65

def datetime_select(name, *hashes)
  default_html = { class: datetime_select_class }
  hashes << { order: %i[ month day year ], ampm: true }
  select_options = {}
  %i[
    include_blank prompt
    ampm order default start_year end_year
  ].each { |key| select_options[key] = extract(key, *hashes) }
  form_builder.datetime_select(name, select_options, extract_html(*hashes, class: datetime_select_class))
end

#extract(key, *hashes) ⇒ Object



28
29
30
31
32
33
34
35
36
# File 'lib/action_presenter/base.rb', line 28

def extract(key, *hashes)
  hashes.compact.map do |hash|
    begin
      hash.delete(key.to_sym) || hash.delete(key.to_s)
    rescue StandardError => error
      raise ArgumentError, "#{error.class} while trying to extract key #{key.inspect} from #{hash.inspect}: #{error.message}"
    end
  end.compact.first
end

#extract_content(*hashes, &content_block) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/action_presenter/base.rb', line 85

def extract_content(*hashes, &content_block)
  content = extract(:content, *hashes)
  # block has highest precedence
  content = view.capture(&content_block) if content_block.is_a?(Proc)
  content = content.to_s if content.is_a?(Numeric)
  # check for escaped HTML
  if presenter_options.verify_content && !content.is_a?(ActiveSupport::SafeBuffer)
    if content =~ /<[a-zA-Z]+( |>)/ || content =~ /&nbsp/
      warning_message = "Escaped HTML in #{self.class} content: #{content}"
      if Rails.env.production?
        Rails.logger.warn { warning_message }
      else
        raise ArgumentError, warning_message
      end
    end
  end
  content
end

#extract_content_block(*hashes, &content_block) ⇒ Object



104
105
106
# File 'lib/action_presenter/base.rb', line 104

def extract_content_block(*hashes, &content_block)
  content_block.is_a?(Proc) ? content_block : proc { extract_content(*hashes) }
end

#extract_html(*hashes) ⇒ Object



38
39
40
41
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
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/action_presenter/base.rb', line 38

def extract_html(*hashes)
  html_attributes = {}
  add_classes = []
  remove_classes = []
  merge_data = {}
  reverse_merge_data = {}
  hashes.each do |hash|
    next if hash.blank?
    # custom class attributes
    add_class = extract(:add_class, hash)
    add_classes << add_class if add_class.present?
    remove_class = extract(:remove_class, hash)
    remove_classes << remove_class if remove_class.present?
    # custom data attributes
    merging_data = extract(:merge_data, hash)
    merge_data.merge!(merging_data.to_h.symbolize_keys) if merging_data.present?
    reverse_merging_data = extract(:reverse_merge_data, hash)
    reverse_merge_data.merge!(reverse_merging_data.to_h.symbolize_keys) if reverse_merging_data.present?
    # rest
    html_attributes.reverse_merge!(hash.symbolize_keys)
  end
  # custom class attributes
  if add_classes.present? || remove_classes.present?
    classes_to_a = -> (value) do
      case value
      when Symbol then classes_to_a.call(send(value))
      when Array then value.map { |v| classes_to_a.call(v) }
      else value.to_s.split(/\s/)
      end.flatten
    end
    classes = classes_to_a.call(html_attributes[:class])
    classes += classes_to_a.call(add_classes)
    classes -= classes_to_a.call(remove_classes)
    # reverse so classes appear to append in order
    html_attributes[:class] = classes.uniq.join(" ").presence
  end
  # custom data attributes
  if merge_data.present? || reverse_merge_data.present?
    html_attributes[:data] ||= {}
    html_attributes[:data].merge!(merge_data) if merge_data.present?
    html_attributes[:data].reverse_merge!(reverse_merge_data) if reverse_merge_data.present?
  end
  html_attributes.select do |html_attribute, value|
    !html_attribute.in?(PRESENTER_ATTRIBUTES)
  end
end

#extract_query_params(*hashes) ⇒ Object



112
113
114
# File 'lib/action_presenter/base.rb', line 112

def extract_query_params(*hashes)
  extract(:query_params, *hashes, query_params: {})
end

#extract_record(*hashes) ⇒ Object



108
109
110
# File 'lib/action_presenter/base.rb', line 108

def extract_record(*hashes)
  extract(:record, *hashes, record: record)
end

#file_field_tag(name, *hashes, &content_block) ⇒ Object



70
71
72
73
# File 'lib/action_presenter/base/html.rb', line 70

def file_field_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: file_field_class }
  view.file_field_tag(name, extract_html(*hashes, default_html))
end

#form_for(*hashes, &content_block) ⇒ Object



12
13
14
15
16
17
18
# File 'lib/action_presenter/base/form.rb', line 12

def form_for(*hashes, &content_block)
  record = extract_record(*hashes)
  view.form_for(record, extract_html(*hashes)) do |form_builder|
    self.form_builder = form_builder
    yield(form_builder)
  end
end

#form_tag(path, *hashes, &content_block) ⇒ Object



5
6
7
8
9
10
# File 'lib/action_presenter/base/form.rb', line 5

def form_tag(path, *hashes, &content_block)
  view.form_tag(path, extract_html(*hashes)) do |form_builder|
    self.form_builder = form_builder
    yield(form_builder)
  end
end

#hidden_field(name, *hashes, &content_block) ⇒ Object



37
38
39
40
# File 'lib/action_presenter/base/form.rb', line 37

def hidden_field(name, *hashes, &content_block)
  default_html = { value: extract_content(*hashes, &content_block) }.compact
  form_builder.hidden_field(name, extract_html(*hashes, default_html))
end

#hidden_field_tag(name, *hashes, &content_block) ⇒ Object



75
76
77
78
79
# File 'lib/action_presenter/base/html.rb', line 75

def hidden_field_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name }
  content = extract_content(*hashes, content: view.params.to_unsafe_h.dig_html(name), &content_block)
  view.hidden_field_tag(name, content, extract_html(*hashes, default_html))
end

#html_tag(tag, *hashes, &content_block) ⇒ Object



4
5
6
# File 'lib/action_presenter/base/html.rb', line 4

def html_tag(tag, *hashes, &content_block)
  view.(tag, extract_html(*hashes), &extract_content_block(*hashes, &content_block))
end

#image_tag(name, *hashes, &content_block) ⇒ Object



46
47
48
# File 'lib/action_presenter/base/html.rb', line 46

def image_tag(name, *hashes, &content_block)
  view.image_tag(name, extract_html(*hashes), &extract_content_block(*hashes, &content_block))
end

#inline_html_tag(tag, *hashes, &content_block) ⇒ Object



8
9
10
# File 'lib/action_presenter/base/html.rb', line 8

def inline_html_tag(tag, *hashes, &content_block)
  view.tag(tag, extract_html(*hashes), &extract_content_block(*hashes, &content_block))
end

#label(name, *hashes, &content_block) ⇒ Object

form elements



27
28
29
30
# File 'lib/action_presenter/base/form.rb', line 27

def label(name, *hashes, &content_block)
  default_html = { class: label_class }
  form_builder.label(name, extract_html(*hashes, default_html), &extract_content_block(*hashes, &content_block))
end

#label_for(name, *hashes, &content_block) ⇒ Object



87
88
89
90
# File 'lib/action_presenter/base/html.rb', line 87

def label_for(name, *hashes, &content_block)
  default_html = { class: label_for_class }
  view.label_tag(name, extract_html(*hashes, default_html), &extract_content_block(*hashes, &content_block))
end


50
51
52
# File 'lib/action_presenter/base/html.rb', line 50

def link_to(link, *hashes, &content_block)
  view.link_to(link, extract_html(*hashes), &extract_content_block(*hashes, &content_block))
end

#method_field(*hashes, &content_block) ⇒ Object



42
43
44
# File 'lib/action_presenter/base/html.rb', line 42

def method_field(*hashes, &content_block)
  hidden_field_tag("_method", *hashes, id: nil, value: "post", &content_block)
end

#presenter_optionsObject



24
25
26
# File 'lib/action_presenter/base.rb', line 24

def presenter_options
  self.class.presenter_options.dup
end

#radio_button(name, *hashes, &content_block) ⇒ Object



83
84
85
86
87
# File 'lib/action_presenter/base/form.rb', line 83

def radio_button(name, *hashes, &content_block)
  default_html = { class: radio_button_class }
  content = extract_content(*hashes, &content_block).to_s
  form_builder.radio_button(name, content, extract_html(*hashes, default_html))
end

#radio_button_tag(name, *hashes, &content_block) ⇒ Object



105
106
107
108
109
110
111
112
# File 'lib/action_presenter/base/html.rb', line 105

def radio_button_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: radio_button_class }
  content = extract_content(*hashes, &content_block).presence
  active_content = view.params.to_unsafe_h.dig_html(name).presence
  checked = extract(:checked, *hashes)
  checked = checked.in?([true, false]) ? checked : active_content == content
  view.radio_button_tag(nil, content, checked, extract_html(*hashes, default_html))
end

#select(name, *hashes, &content_block) ⇒ Object



47
48
49
50
51
52
# File 'lib/action_presenter/base/form.rb', line 47

def select(name, *hashes, &content_block)
  default_html = { class: select_class }
  content = extract_content(*hashes, &content_block) || {}
  select_options = %i[ include_blank prompt ].map { |key| [key, extract(key, *hashes)] }.to_h
  form_builder.select(name, content, select_options, extract_html(*hashes, default_html))
end

#select_tag(name, *hashes, &content_block) ⇒ Object

form elements



55
56
57
58
59
60
61
62
# File 'lib/action_presenter/base/html.rb', line 55

def select_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: select_class }
  content = extract_content(*hashes, &content_block)
  if !content.is_a?(String)
    content = view.options_for_select(content, extract(:selected, *hashes, selected: view.params.to_unsafe_h.dig_html(name)))
  end
  view.select_tag(name, content, extract_html(*hashes, default_html))
end

#set(options) ⇒ Object



19
20
21
22
# File 'lib/action_presenter/base.rb', line 19

def set(options)
  options.each { |method, value| send "#{method}=", value }
  self
end

#submit_tag(*hashes, &content_block) ⇒ Object



38
39
40
# File 'lib/action_presenter/base/html.rb', line 38

def submit_tag(*hashes, &content_block)
  view.submit_tag(extract_content(*hashes, content: "Submit", &content_block), extract_html(*hashes))
end

#text_area(name, *hashes, &content_block) ⇒ Object



42
43
44
45
# File 'lib/action_presenter/base/form.rb', line 42

def text_area(name, *hashes, &content_block)
  default_html = { class: text_area_class, value: extract_content(*hashes, &content_block) }.compact
  form_builder.text_area(name, extract_html(*hashes, default_html))
end

#text_area_tag(name, *hashes, &content_block) ⇒ Object



81
82
83
84
85
# File 'lib/action_presenter/base/html.rb', line 81

def text_area_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: text_area_class }
  content = extract_content(*hashes, content: view.params.to_unsafe_h.dig_html(name), &content_block)
  view.text_area_tag(name, content, extract_html(*hashes, default_html))
end

#text_field(name, *hashes, &content_block) ⇒ Object



32
33
34
35
# File 'lib/action_presenter/base/form.rb', line 32

def text_field(name, *hashes, &content_block)
  default_html = { class: text_field_class, value: extract_content(*hashes, &content_block) }.compact
  form_builder.text_field(name, extract_html(*hashes, default_html))
end

#text_field_tag(name, *hashes, &content_block) ⇒ Object



64
65
66
67
68
# File 'lib/action_presenter/base/html.rb', line 64

def text_field_tag(name, *hashes, &content_block)
  default_html = { id: name.to_s.slugify_html, name: name, class: text_field_class }
  content = extract_content(*hashes, content: view.params.to_unsafe_h.dig_html(name), &content_block)
  view.text_field_tag(name, content, extract_html(*hashes, default_html))
end