lib/active_scaffold/helpers/action_link_helpers.rb
module ActiveScaffold
module Helpers
# All extra helpers that should be included in the View.
# Also a dumping ground for uncategorized helpers.
module ActionLinkHelpers
# params which mustn't be copying to nested links
NESTED_PARAMS = %i[eid embedded association parent_scaffold].freeze
def skip_action_link?(link, *args)
!link.ignore_method.nil? && controller.respond_to?(link.ignore_method, true) && controller.send(link.ignore_method, *args)
end
def action_link_authorized?(link, *args)
auth, reason =
if link.security_method_set? || controller.respond_to?(link.security_method, true)
controller.send(link.security_method, *args)
else
args.empty? ? true : args.first.authorized_for?(:crud_type => link.crud_type, :action => link.action, :reason => true)
end
[auth, reason]
end
def display_dynamic_action_group(action_link, links, record_or_ul_options = nil, ul_options = nil)
ul_options = record_or_ul_options if ul_options.nil? && record_or_ul_options.is_a?(Hash)
record = record_or_ul_options unless record_or_ul_options.is_a?(Hash)
html = content_tag :ul, ul_options do
safe_join(links.map { |link| content_tag :li, link })
end
raw "ActiveScaffold.display_dynamic_action_group('#{get_action_link_id action_link, record}', '#{escape_javascript html}');" # rubocop:disable Rails/OutputSafety
end
def display_action_links(action_links, record, options, &block)
options[:level_0_tag] ||= nil
options[:options_level_0_tag] ||= nil
options[:level] ||= 0
options[:first_action] = true
output = ActiveSupport::SafeBuffer.new
action_links.each(:reverse => options.delete(:reverse), :groups => true) do |link|
if link.is_a? ActiveScaffold::DataStructures::ActionLinks
unless link.empty?
options[:level] += 1
content = display_action_links(link, record, options, &block)
options[:level] -= 1
if content.present?
output << display_action_link(link, content, record, options)
options[:first_action] = false
end
end
elsif !skip_action_link?(link, *Array(options[:for]))
authorized, reason = action_link_authorized?(link, *Array(options[:for]))
next if !authorized && options[:skip_unauthorized]
output << display_action_link(link, nil, record, options.merge(:authorized => authorized, :not_authorized_reason => reason))
options[:first_action] = false
end
end
output
end
def display_action_link(link, content, record, options)
if content
html_classes = hover_via_click? ? 'hover_click ' : ''
if (options[:level]).zero?
html_classes << 'action_group'
group_tag = :div
else
html_classes << 'top' if options[:first_action]
group_tag = :li
end
content = content_tag(group_tag, :class => html_classes.presence, :onclick => ('' if hover_via_click?)) do
content_tag(:div, as_(link.label), :class => link.name.to_s.downcase) << content_tag(:ul, content)
end
else
content = render_action_link(link, record, options)
content = content_tag(:li, content, :class => ('top' if options[:first_action])) unless (options[:level]).zero?
end
content = content_tag(options[:level_0_tag], content, options[:options_level_0_tag]) if (options[:level]).zero? && options[:level_0_tag]
content
end
def render_action_link(link, record = nil, options = {})
if link.action.nil? || link.column&.association&.polymorphic?
link = action_link_to_inline_form(link, record) if link.column&.association
options[:authorized] = false if link.action.nil? || link.controller.nil?
options.delete :link if link.crud_type == :create
end
if link.action.nil? || (link.type == :member && options.key?(:authorized) && !options[:authorized])
html_class = "disabled #{link.action}#{" #{link.html_options[:class]}" if link.html_options[:class].present?}"
html_options = {:link => action_link_text(link, options), :class => html_class, :title => options[:not_authorized_reason]}
action_link_html(link, nil, html_options, record)
else
url = action_link_url(link, record)
html_options = action_link_html_options(link, record, options)
action_link_html(link, url, html_options, record)
end
end
# setup the action link to inline form
def action_link_to_inline_form(link, record)
link = link.dup
associated = record.send(link.column.association.name)
if link.column.association&.polymorphic?
link.controller = controller_path_for_activerecord(associated.class)
return link if link.controller.nil?
end
link = configure_column_link(link, record, associated) if link.action.nil?
link
end
def configure_column_link(link, record, associated, actions = nil)
actions ||= link.controller_actions || []
if column_empty?(associated) # if association is empty, we only can link to create form
if actions.include?(:new)
link.action = 'new'
link.crud_type = :create
link.label ||= :create_new
end
elsif actions.include?(:edit)
link.action = 'edit'
link.crud_type = :update
elsif actions.include?(:show)
link.action = 'show'
link.crud_type = :read
elsif actions.include?(:list)
link.action = 'index'
link.crud_type = :read
end
unless column_link_authorized?(link, link.column, record, associated)[0]
link.action = nil
# if action is edit and is not authorized, fallback to show if it's enabled
if link.crud_type == :update && actions.include?(:show)
link = configure_column_link(link, record, associated, [:show])
end
end
link
end
def column_link_authorized?(link, column, record, associated)
if column.association
associated_for_authorized =
if column.association.collection? || associated.nil?
column.association.klass
else
associated
end
authorized, reason = associated_for_authorized.authorized_for?(:crud_type => link.crud_type, :reason => true)
if link.crud_type == :create && authorized
authorized, reason = record.authorized_for?(:crud_type => :update, :column => column.name, :reason => true)
end
[authorized, reason]
else
action_link_authorized?(link, record)
end
end
def sti_record?(record)
return unless active_scaffold_config.active_record?
model = active_scaffold_config.model
record && model.columns_hash.include?(model.inheritance_column) &&
record[model.inheritance_column].present? && !record.instance_of?(model)
end
def cache_action_link_url?(link, record)
active_scaffold_config.user.cache_action_link_urls && link.type == :member && !link.dynamic_parameters.is_a?(Proc) && !sti_record?(record)
end
def cached_action_link_url(link, record)
@action_links_urls ||= {}
@action_links_urls[link.name_to_cache.to_s] || begin
url_options = cached_action_link_url_options(link, record)
if cache_action_link_url?(link, record)
@action_links_urls[link.name_to_cache.to_s] = url_for(url_options)
else
url_options.merge! eid: nil, embedded: nil if link.nested_link?
url_for(params_for(url_options))
end
end
end
def replace_id_params_in_action_link_url(link, record, url)
url = record ? url.sub('--ID--', record.to_param.to_s) : url.clone
if link.column&.association&.singular?
child_id = record.send(link.column.association.name)&.to_param
if child_id.present?
url.sub!('--CHILD_ID--', child_id)
else
url.sub!(/\w+=--CHILD_ID--&?/, '')
url.sub!(/\?$/, '')
end
elsif nested?
url.sub!('--CHILD_ID--', params[nested.param_name].to_s)
end
url
end
def add_query_string_to_cached_url(link, url)
query_string, non_nested_query_string = query_string_for_action_links(link)
nested_params = (!link.nested_link? && non_nested_query_string)
if query_string || nested_params
url << (url.include?('?') ? '&' : '?')
url << query_string if query_string
url << non_nested_query_string if nested_params
end
url
end
def action_link_url(link, record)
url = replace_id_params_in_action_link_url(link, record, cached_action_link_url(link, record))
url = add_query_string_to_cached_url(link, url) if @action_links_urls[link.name_to_cache.to_s]
url
end
def column_in_params_conditions?(key)
if key.match?(/!$/)
conditions_from_params[1..-1].any? { |node| node.left.name.to_s == key[0..-2] }
else
conditions_from_params[0].include?(key)
end
end
def ignore_param_for_nested?(key)
NESTED_PARAMS.include?(key) || column_in_params_conditions?(key) || (nested? && nested.param_name == key)
end
def query_string_for_action_links(link)
if defined?(@query_string) && link.parameters.none? { |k, _| @query_string_params.include? k }
return [@query_string, @non_nested_query_string]
end
keep = true
@query_string_params ||= Set.new
query_string_options = {}
non_nested_query_string_options = {}
params_for.except(:controller, :action, :id).each do |key, value|
@query_string_params << key
if link.parameters.include? key
keep = false
next
end
if ignore_param_for_nested?(key)
non_nested_query_string_options[key] = value
else
query_string_options[key] = value
end
end
if nested_singular_association? && action_name == 'index'
# pass current path as return_to, for nested listing on singular association, so forms doesn't return to parent listing
@query_string_params << :return_to
non_nested_query_string_options[:return_to] = request.fullpath
end
query_string = query_string_options.to_query if query_string_options.present?
if non_nested_query_string_options.present?
non_nested_query_string = "#{'&' if query_string}#{non_nested_query_string_options.to_query}"
end
if keep
@query_string = query_string
@non_nested_query_string = non_nested_query_string
end
[query_string, non_nested_query_string]
end
def cache_action_link_url_options?(link, record)
active_scaffold_config.user.cache_action_link_urls && (link.type == :collection || !link.dynamic_parameters.is_a?(Proc)) && !sti_record?(record)
end
def cached_action_link_url_options(link, record)
@action_links_url_options ||= {}
@action_links_url_options[link.name_to_cache.to_s] || begin
options = action_link_url_options(link, record)
if cache_action_link_url_options?(link, record)
@action_links_url_options[link.name_to_cache.to_s] = options
end
options
end
end
def action_link_url_options(link, record)
url_options = {:action => link.action}
url_options[:id] = '--ID--' unless record.nil?
url_options[:controller] = link.controller.to_s if link.controller
url_options.merge! link.parameters if link.parameters
if link.dynamic_parameters.is_a?(Proc)
if record.nil?
url_options.merge! instance_exec(&link.dynamic_parameters)
else
url_options.merge! instance_exec(record, &link.dynamic_parameters)
end
end
if link.nested_link?
url_options_for_nested_link(link.column, record, link, url_options)
elsif nested?
url_options[nested.param_name] = '--CHILD_ID--'
end
url_options_for_sti_link(link.column, record, link, url_options) unless record.nil? || active_scaffold_config.sti_children.nil?
url_options[:_method] = link.method if !link.confirm? && link.inline? && link.method != :get
url_options
end
def action_link_text(link, options)
text = image_tag(link.image[:name], :size => link.image[:size], :alt => options[:link] || link.label, :title => options[:link] || link.label) if link.image
text || options[:link]
end
def replaced_action_link_url_options(link, record)
url = cached_action_link_url_options(link, record)
url[:controller] ||= params[:controller]
missing_options, url_options = url.partition { |_, v| v.nil? }
replacements = {}
replacements['--ID--'] = record.id.to_s if record
if link.column&.association&.singular?
replacements['--CHILD_ID--'] = record.send(link.column.association.name)&.id.to_s
elsif nested?
replacements['--CHILD_ID--'] = params[nested.param_name].to_s
end
url_options.collect! do |k, v|
[k.to_s, replacements[v] || v]
end
[missing_options, url_options]
end
def action_link_selected?(link, record)
missing_options, url_options = replaced_action_link_url_options(link, record)
safe_params = params.to_unsafe_h
(url_options - safe_params.to_a).blank? && missing_options.all? { |k, _| params[k].nil? }
end
def action_link_html_options(link, record, options)
link_id = get_action_link_id(link, record)
html_options = link.html_options.merge(:class => [link.html_options[:class], link.action.to_s].compact.join(' '))
html_options[:link] = action_link_text(link, options)
# Needs to be in html_options to as the adding _method to the url is no longer supported by Rails
html_options[:method] = link.method if link.method != :get
html_options[:data] ||= {}
html_options[:data] = html_options[:data].deep_dup if html_options[:data].frozen?
html_options[:data][:confirm] = link.confirm(h(record&.to_label)) if link.confirm?
if !options[:page] && !options[:popup] && (options[:inline] || link.inline?)
html_options[:class] << ' as_action'
html_options[:data][:position] = link.position if link.position
html_options[:data][:action] = link.action
html_options[:data][:cancel_refresh] = true if link.refresh_on_close
html_options[:data][:keep_open] = true if link.keep_open?
html_options[:remote] = true
end
if link.toggle
html_options[:class] << ' toggle'
html_options[:class] << ' active' if action_link_selected?(link, record)
end
if !options[:page] && !options[:inline] && (options[:popup] || link.popup?)
html_options[:target] = '_blank'
html_options[:rel] = [html_options[:rel], 'noopener noreferrer'].compact.join(' ')
end
html_options[:id] = link_id
if link.dhtml_confirm?
unless link.inline?
html_options[:class] << ' as_action'
html_options[:page_link] = 'true'
end
html_options[:dhtml_confirm] = link.dhtml_confirm.value
html_options[:onclick] = link.dhtml_confirm.onclick_function(controller, link_id)
end
html_options
end
def get_action_link_id(link, record = nil)
column = link.column
if column&.association && record
associated = record.send(column.association.name) unless column.association.collection?
id =
if associated
"#{column.association.name}-#{associated.id}-#{record.id}"
else
"#{column.association.name}-#{record.id}"
end
end
id ||= record&.id&.to_s || (nested? ? nested_parent_id.to_s : '')
action_link_id = ActiveScaffold::Registry.cache :action_link_id, link.name_to_cache.to_s do
action_id = "#{id_from_controller("#{link.controller}-") if params[:parent_controller] || (link.controller && link.controller != controller.controller_path)}#{link.action}"
action_link_id(action_id, '--ID--')
end
action_link_id.sub('--ID--', id)
end
def action_link_html(link, url, html_options, record)
label = html_options.delete(:link)
label ||= link.label
if url.nil?
content_tag(:a, label, html_options)
else
link_to(label, url, html_options)
end
end
def url_options_for_nested_link(column, record, link, url_options)
if column&.association
url_options[:parent_scaffold] = controller_path
url_options[column.model.name.foreign_key.to_sym] = url_options.delete(:id)
url_options[:id] = if column.association.singular? && url_options[:action].to_sym != :index
'--CHILD_ID--'
end
elsif link.parameters&.dig(:named_scope)
url_options[:parent_scaffold] = controller_path
url_options[active_scaffold_config.model.name.foreign_key.to_sym] = url_options.delete(:id)
url_options[:id] = nil
end
end
def url_options_for_sti_link(column, record, link, url_options)
# need to find out controller of current record type and set parameters
# it's quite difficult to detect an sti link
# if link.column.nil? we are sure that it isn't a singular association inline autolink
# however that will not work if a sti parent is a singular association inline autolink
return unless link.column.nil?
return if (sti_controller_path = controller_path_for_activerecord(record.class)).nil?
url_options[:controller] = sti_controller_path
url_options[:parent_sti] = controller_path
end
end
end
end