class Object

Constants

MissingApplicationController
MissingApplicationMailer

Public Class Methods

access_tests_for(action, options = {}) click to toggle source

Tests access permissions for a specific action.

Options:

  • controller is the string name of the controller. If not supplied, the controller is inferred from the class name.

  • url_helper is a string to generate the url. If not supplied, the helper is inferred from the controller and action names.

  • fixture_helper is a string defining the fixture helper to use. If not supplied the controller name will be used.

  • fixture_key is the fixture key to use. Defaults to :one.

  • allow_anon determines if anonymous users should be able to access the action. Default is false.

  • allow_any_user determines if any logged in user should be able to access the action. Default is false.

  • allow_groups determines if a specific set of groups can access the action. Default is nil.

  • deny_groups determines if a specific set of groups should not be able to access the action. Default is nil.

  • allow_admin determines if system admins can access the action. Default is true.

  • method determines the method to process the action with. Default is ‘get’.

  • success determines the result on success. Defaults to :success for ‘get’ requests, otherwise the pluralized controller helper path.

  • failure determines the result on failure for non-anon tests. Defaults to ‘root_url’.

  • anon_failure determines the result on failure for anon tests. Defaults to ‘login_url’.

# File lib/barkest_core/extensions/test_case_extensions.rb, line 188
def self.access_tests_for(action, options = {})

  if action.respond_to?(:each)
    action.each do |act|
      access_tests_for(act, options.dup)
    end
    return
  end

  options = {
      allow_anon: false,
      allow_any_user: false,
      allow_groups: nil,
      allow_admin: true,
      fixture_key: :one,
      failure: 'root_url',
      anon_failure: 'login_url'
  }.merge(options || {})

  action = action.to_sym
  params = options[:"#{action}_params"]
  params = nil unless params.is_a?(Hash)

  if options[:method].blank?
    options[:method] =
        if action == :destroy
          'delete'
        elsif action == :update
          'patch'
        elsif action == :create
          'post'
        else
          'get'
        end
  end

  if options[:controller].blank?
    options[:controller] = self.name.underscore.rpartition('_')[0]
  else
    options[:controller] = options[:controller].to_s.underscore
  end

  if options[:controller][-11..-1] == '_controller'
    options[:controller] = options[:controller].rpartition('_')[0]
  end

  if options[:fixture_helper].blank?
    options[:fixture_helper] = options[:controller]
  end

  options[:method] = options[:method].to_sym

  if options[:url_helper].blank?
    fix_val = "#{options[:fixture_helper].pluralize}(#{options[:fixture_key].inspect})"
    options[:url_helper] =
        case action
          when :show, :update, :destroy   then  "#{options[:controller].singularize}_path(#{fix_val})"
          when :edit                      then  "edit_#{options[:controller].singularize}_path(#{fix_val})"
          when :new                       then  "new_#{options[:controller].singularize}_path"
          else                                  "#{options[:controller].pluralize}_path"
        end
  end

  if options[:success].blank?
    if options[:method] == :get
      options[:success] = :success
    else
      options[:success] = "#{options[:controller].pluralize}_path"
    end
  end


  method = options[:method]
  url_helper = options[:url_helper]

  tests = [
      [ 'anonymous', options[:allow_anon],      nil,      nil,    nil,    options[:anon_failure] ],
      [ 'any user',  options[:allow_any_user],  :basic ],
      [ 'admin user', options[:allow_admin],    :admin ]
  ]

  unless options[:allow_groups].blank?
    if options[:allow_groups].is_a?(String)
      options[:allow_groups] = options[:allow_groups].gsub(',', ';').split(';').map{|v| v.strip}
    end
    options[:allow_groups].each do |group|
      tests << [ "#{group} member", true, :basic, group ]
    end
  end

  unless options[:deny_groups].blank?
    if options[:deny_groups].is_a?(String)
      options[:deny_groups] = options[:deny_groups].gsub(',', ';').split(';').map{|v| v.strip}
    end
    options[:deny_groups].each do |group|
      tests << [ "#{group} member", false, :basic, group ]
    end
  end

  tests.each do |(label, result, user, group, success_override, failure_override)|
    expected_result = result ? (success_override || options[:success]) : (failure_override || options[:failure])

    test_code = "test \"should #{result ? '' : 'not '}allow access to #{action} for #{label}\" do\n"

    if user
      test_code += "user = users(#{user.inspect})\n"
      if group
        test_code += "group = AccessGroup.get(#{group.inspect}) || AccessGroup.create(name: #{group.inspect})\n"
        test_code += "user.groups << group\n"
      end
      test_code += "log_in_as user\n"
    end

    test_code += "path = #{url_helper}\n"

    if params.blank?
      test_code += "#{method} path\n"
    else
      test_code += "#{method} path, #{params.inspect[1...-1]}\n"
    end

    if expected_result.is_a?(Symbol)
      test_code += "assert_response #{expected_result.inspect}\n"
    else
      test_code += "assert_redirected_to #{expected_result}\n"
    end

    test_code += "end\n"

    eval test_code
  end

end
add_concerns(subdir = nil) click to toggle source

Loads the concerns for the current model.

# File lib/barkest_core/extensions/active_record_extensions.rb, line 77
def self.add_concerns(subdir = nil)
  klass = self
  subdir ||= klass.name.underscore

  Dir.glob(File.expand_path("../../../app/models/concerns/#{subdir}/*.rb", __FILE__)).each do |item|
    require item
    mod_name = File.basename(item)[0...-3].camelcase
    if const_defined? mod_name
      mod_name = const_get mod_name
      klass.include mod_name
    else
      raise StandardError.new("The #{mod_name} module does not appear to be defined.")
    end
  end
end
load_first(*args) click to toggle source

Determines the fixtures that will be loaded first.

Arguments can either be just table names or a hash of table names with indexes. If just table names, then the tables are inserted at the end of the list. If a hash, then the value is the index you want the table to appear in the load list.

BarkestCore tables are pre-indexed to load before any other tables.

Usage:

ActiveRecord::FixtureSet.load_first :table_1, :table_2
ActiveRecord::FixtureSet.load_first :table_1 => 0, :table_2 => 1
# File lib/barkest_core/extensions/fixture_set_extensions.rb, line 22
def self.load_first(*args)
  priority_list = %w(access_groups users)

  @load_first ||= priority_list

  unless args.blank?
    args.each do |arg|
      if arg.is_a?(Hash)
        arg.each do |fix,order|
          fix = fix.to_s
          order += priority_list.length
          if order >= @load_first.length
            @load_first << fix unless @load_first.include?(fix)
          else
            @load_first.insert(order, fix) unless @load_first.include?(fix)
          end
        end
      else
        fix = arg.to_s
        @load_first << fix unless @load_first.include?(fix)
      end
    end
  end

  @load_first
end
purge_first(*args) click to toggle source

Determines the fixtures that will purged first.

Arguments can either be just table names or a hash of table names with indexes. If just table names then the tables are inserted at the beginning of the list. If a hash, then the value is the index you want the table to appear in the purge list.

BarkestCore tables are pre-indexed to be purged after any other tables added to this list.

Usage:

ActiveRecord::FixtureSet.purge_first :table_1, :table_2
ActiveRecord::FixtureSet.purge_first :table_1 => 0, :table_2 => 1
# File lib/barkest_core/extensions/fixture_set_extensions.rb, line 61
def self.purge_first(*args)
  priority_list = %w(ldap_access_groups access_group_user_members access_group_group_members user_login_histories users access_groups)

  @purge_first ||= priority_list

  unless args.blank?
    args.reverse.each do |arg|
      if arg.is_a?(Hash)
        arg.each do |fix,order|
          fix = fix.to_s
          if order <= 0
            @purge_first.insert(0, fix) unless @purge_first.include?(fix)
          elsif order >= @purge_first.length - priority_list.length
            @purge_first.insert(@purge_first.length - priority_list.length, fix) unless @purge_first.include?(fix)
          else
            @purge_first.insert(order, fix) unless @purge_first.include?(fix)
          end
        end
      else
        fix = arg.to_s
        @purge_first.insert(0, fix) unless @purge_first.include?(fix)
      end
    end
  end

  @purge_first
end
utc_parse(s) click to toggle source

Parses a time string into UTC time.

Supports either M/D/Y H:M:S or Y-M-D H:M:S format.

If a timezone is provided, it will be taken into account and the UTC equivalent will be returned.

# File lib/barkest_core/extensions/time_extensions.rb, line 16
def self.utc_parse(s)
  raise ArgumentError, 'expected a time value to be provided' if s.blank?

  # If it's already a time, return the UTC variant.
  return s.utc if s.is_a?(Time)
  # If it can be converted to a time, and is not a string, convert and return the UTC variant.
  return s.to_time.utc if s.respond_to?(:to_time) && !s.is_a?(String)
  # If it is not a string, turn it into a string and parse that.
  return utc_parse(s.to_s) unless s.is_a?(String)

  dt,tm,tz,ex = s.split(' ')

  # Time only?
  if dt.include?(':')
    ex = tz
    tz = tm
    tm = dt
    dt = '1900-01-01'
  end

  yr,mon,day =
      if dt.include?('/')
        # M/D/Y
        _m,_d,_y = dt.split('/')
        _y ||= Time.now.utc.year
        [ _y, _m, _d ]
      elsif dt.include?('-')
        # Y-M-D
        dt.split('-')

        # Because we may be interacting with Spectrum users, accept these formats as well.
      elsif (/^\d{4}$/).match(dt)
        # MMDD
        [ Time.now.utc.year, dt[0...2], dt[2...4] ]
      elsif (/^\d{6}(\d{2})?$/).match(dt)
        # MMDDYY(YY)
        [ dt[4..-1], dt[0...2], dt[2...4] ]

      elsif (/^\d+(am|pm)?$/i).match(dt) && (0..24).include?(dt.to_i)
        # a single integer parsing to a valid hour, possibly with an AM/PM specifier
        ex = tz
        tz = tm
        tm = dt
        [ 1900, 1, 1 ]

      else
        # Unsupported format.
        []
      end.map { |i| i.to_i }

  raise ArgumentError, 'year is missing' unless yr
  raise ArgumentError, 'month is missing' unless mon
  raise ArgumentError, 'day is missing' unless day

  yr += 2000 if yr < 40
  yr += 1900 if yr < 100

  mon = 1 unless mon
  day = 1 unless day

  if tm
    # allow AM/PM specifier to be attached to time.
    if %w(AM PM).include?(tm[-2..-1].to_s.upcase)
      ex = tz
      tz = tm[-2..-1]
    end
  end

  hr,min,sec = tm ? tm.split(':') : []
  hr = hr ? hr.to_i : 0
  min = min ? min.to_i : 0
  sec = sec ? sec.to_f : 0.0

  if %w(AM PM).include?(tz.to_s.upcase)
    raise ArgumentError, 'hour must be between 1 and 12 for 12-hour formats' unless (1..12).include?(hr)

    tz = tz.to_s.upcase

    if tz == 'AM'
      hr = 0 if hr == 12    # only need to modify midnight.
    else
      hr += 12 unless hr == 12  # modify everything except noon.
    end

    # grab the next item from the original string.
    tz = ex
  end

  if hr == 24 && min == 0 && sec == 0
    tmp_date = Time.utc(yr,mon,day)
    raise ArgumentError, 'time component overflow' unless tmp_date.year == yr && tmp_date.month == mon && tmp_date.day == day
    tmp_date = (tmp_date + 1.day).to_time.utc
    yr = tmp_date.year
    mon = tmp_date.month
    day = tmp_date.day
    hr = 0
  end

  tz = nil if %w(UTC).include?(tz.to_s.upcase)

  result =
    if tz
      bits = (/^([+-])(\d{1,2}):?(\d{2})$/).match(tz)
      raise ArgumentError, '"+HH:MM" or "-HH:MM" expected for utc_offset' unless bits
      tz = "#{bits[1]}#{bits[2].rjust(2,'0')}:#{bits[3]}"
      Time.new(yr, mon, day, hr, min, sec, tz)
    else
      Time.utc(yr, mon, day, hr, min, sec)
    end

  raise ArgumentError, 'time component overflow' unless result.year == yr && result.month == mon && result.day == day && result.hour == hr && result.min == min && result.sec.to_i == sec.to_i

  result.utc
end

Public Instance Methods

==(other) click to toggle source

Tests for equality on ID.

# File lib/barkest_core/extensions/active_record_extensions.rb, line 61
def ==(other)
  if respond_to?(:id)
    if other.is_a?(Numeric)
      id == other
    elsif other.class == self.class
      id == other.id
    else
      false
    end
  else
    self.inspect == other.inspect
  end
end
app_company() click to toggle source

Gets the company responsible for the application.

This should be overridden in your application.rb file.

# File lib/barkest_core/extensions/application_extensions.rb, line 38
def app_company
  'BarkerEST'
end
app_info() click to toggle source

Gets the application name and version.

This can be overridden in your application.rb file if you want a different behavior.

# File lib/barkest_core/extensions/application_extensions.rb, line 46
def app_info
  "#{app_name} v#{app_version}"
end
app_name() click to toggle source

Gets the application name.

This should be overridden in your application.rb file.

# File lib/barkest_core/extensions/application_extensions.rb, line 22
def app_name
  'BarkerEST'
end
app_version() click to toggle source

Gets the application version.

This should be overridden in your application.rb file.

# File lib/barkest_core/extensions/application_extensions.rb, line 30
def app_version
  '0.0.1'
end
as_utc() click to toggle source

Discards time zone information and makes

The current time zone offset is discarded. For instance, ‘2016-12-19 19:00:00 -05:00’ becomes ‘2016-12-19 19:00:00 +00:00’

# File lib/barkest_core/extensions/time_extensions.rb, line 144
def as_utc
  Time.utc(year, month, day, hour, min, sec)
end
ask_for_bool(question, default = false) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 62
def ask_for_bool(question, default = false)
  return default if options.quiet?

  print "#{trimq(question)} [#{default ? 'Y/n' : 'y/N'}]? "

  if options.force?
    puts default ? 'Y' : 'N'
    return default
  end

  answer = STDIN.gets.strip.upcase[0]

  return default if answer.blank?

  answer == 'Y'
end
ask_for_int(question, default = 0, valid = nil) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 111
def ask_for_int(question, default = 0, valid = nil)
  return default if options.quiet?

  loop do
    print "#{question} [#{default}]? "

    if options.force?
      puts default.to_s
      return default
    end

    answer = STDIN.gets.strip

    return default if answer.blank?

    answer = answer.to_i

    if valid && valid.respond_to?(:call)
      return answer if valid.call(answer)
    elsif valid && valid.respond_to?(:include?)
      return answer if valid.include?(answer)
    else
      return answer
    end

    puts "Entered value (#{answer}) is invalid."
    unless valid.respond_to?(:call)
      puts "Valid values are #{valid.inspect}."
    end
  end
end
ask_for_secret(question, default = '') click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 143
def ask_for_secret(question, default = '')
  return default if options.quiet?

  print "#{question} [enter to keep, . to clear]: "

  if options.force?
    puts ''
    return default
  end

  loop do
    answer1 = STDIN.noecho(&:gets).strip
    puts ''

    return '' if answer1 == '.'
    return default if answer1.blank?

    print 'Enter again to confirm: '
    answer2 = STDIN.noecho(&:gets).strip
    puts ''

    return answer1 if answer2 == answer1

    puts 'Confirmation does not match!'
    print 'Please try again: '
  end
end
ask_for_secret_key_base(question, default = '') click to toggle source
# File lib/generators/barkest_core/actions/09_configure_secrets.rb, line 21
def ask_for_secret_key_base(question, default = '')
  default = default.to_s
  puts "Current secret key base: #{default[0...20]}..." unless options.quiet?
  tell 'Changing the secret key base will invalidate encrypted values.', :yellow
  return default unless ask_for_bool('Do you want to change the secret key base to a new random value?', false)
  SecureRandom.urlsafe_base64(72)
end
ask_for_string(question, default = '', valid = nil) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 79
def ask_for_string(question, default = '', valid = nil)
  return default if options.quiet?

  loop do
    print "#{trimq(question)} [#{default}] (. to clear)? "

    if options.force?
      puts default.to_s
      return default
    end

    answer = STDIN.gets.strip

    return '' if answer == '.'
    return default if answer.blank?

    if valid && valid.respond_to?(:call)
      return answer if valid.call(answer)
    elsif valid && valid.respond_to?(:include?)
      return answer if valid.include?(answer)
    else
      return answer
    end

    puts "Entered value (#{answer}) is invalid."
    unless valid.respond_to?(:call)
      puts "Valid values are #{valid.inspect}."
    end
  end

end
assert_max_length(model, attribute, max_length, message = nil, options = {}) click to toggle source

Tests a specific field for maximum length restriction.

model must respond to attribute and attribute= as well as valid?.

attribute must provide the name of a valid attribute in the model.

max_length is the maximum valid length for the field.

message is optional, but if provided it will be postfixed with the failure reason.

# File lib/barkest_core/extensions/test_case_extensions.rb, line 48
def assert_max_length(model, attribute, max_length, message = nil, options = {})
  original_value = model.send(attribute)
  original_valid = model.valid?
  setter = :"#{attribute}="

  if message.is_a?(Hash)
    options = message.merge(options || {})
    message = nil
  end

  pre = options[:start].to_s
  post = options[:end].to_s
  len = max_length - pre.length - post.length

  # try with maximum valid length.
  value = pre + ('a' * len) + post
  model.send setter, value
  assert model.valid?, message ? (message + ": !(#{value.length})") : "Should allow a string of #{value.length} characters."

  # try with one extra character.
  value = pre + ('a' * (len + 1)) + post
  model.send setter, value
  assert_not model.valid?, message ? (message + ": (#{value.length})") : "Should not allow a string of #{value.length} characters."

  model.send setter, original_value
  if original_valid
    assert model.valid?, message ? (message + ": !(#{original_value.inspect})") : "Should allow #{attribute} to be set back to '#{original_value.inspect}'."
  end
end
assert_min_length(model, attribute, min_length, message = nil, options = {}) click to toggle source

Tests a specific field for maximum length restriction.

model must respond to attribute and attribute= as well as valid?.

attribute must provide the name of a valid attribute in the model.

max_length is the maximum valid length for the field.

message is optional, but if provided it will be postfixed with the failure reason.

# File lib/barkest_core/extensions/test_case_extensions.rb, line 88
def assert_min_length(model, attribute, min_length, message = nil, options = {})
  original_value = model.send(attribute)
  original_valid = model.valid?
  setter = :"#{attribute}="

  if message.is_a?(Hash)
    options = message.merge(options || {})
    message = nil
  end

  pre = options[:start].to_s
  post = options[:end].to_s
  len = max_length - pre.length - post.length

  # try with minimum valid length.
  value = pre + ('a' * len) + post
  model.send setter, value
  assert model.valid?, message ? (message + ": !(#{value.length})") : "Should allow a string of #{value.length} characters."

  # try with one extra character.
  value = pre + ('a' * (len - 1)) + post
  model.send setter, value
  assert_not model.valid?, message ? (message + ": (#{value.length})") : "Should not allow a string of #{value.length} characters."

  model.send setter, original_value
  if original_valid
    assert model.valid?, message ? (message + ": !(#{original_value.inspect})") : "Should allow #{attribute} to be set back to '#{original_value.inspect}'."
  end
end
assert_required(model, attribute, message = nil) click to toggle source

Tests a specific field for presence validation.

model must respond to attribute and attribute= as well as valid?.

attribute must provide the name of a valid attribute in the model.

message is optional, but if provided it will be postfixed with the failure reason.

# File lib/barkest_core/extensions/test_case_extensions.rb, line 19
def assert_required(model, attribute, message = nil)
  original_value = model.send(attribute)
  original_valid = model.valid?
  is_string = original_value.is_a?(String)
  setter = :"#{attribute}="
  model.send setter, nil
  assert_not model.valid?, message ? (message + ': (nil)') : "Should not allow #{attribute} to be set to nil."
  if is_string
    model.send setter, ''
    assert_not model.valid?, message ? (message + ": ('')") : "Should not allow #{attribute} to be set to empty string."
    model.send setter, '   '
    assert_not model.valid?, message ? (message + ": ('   ')") : "Should not allow #{attribute} to be set to blank string."
  end
  model.send setter, original_value
  if original_valid
    assert model.valid?, message ? (message + ": !(#{original_value.inspect})") : "Should allow #{attribute} to be set back to '#{original_value.inspect}'."
  end
end
assert_uniqueness(model, attribute, case_sensitive = false, message = nil, alternate_scopes = {}) click to toggle source

Tests a specific field for uniqueness.

model must respond to attribute and attribute= as well as valid?. The model will be saved to perform uniqueness testing.

attribute must provide the name of a valid attribute in the model.

case_sensitive determines if changing case should change validation.

message is optional, but if provided it will be postfixed with the failure reason.

alternate_scopes is also optional. If provided the keys of the hash will be used to set additional attributes on the model. When these attributes are changed to the alternate values, the model should once again be valid.

# File lib/barkest_core/extensions/test_case_extensions.rb, line 133
def assert_uniqueness(model, attribute, case_sensitive = false, message = nil, alternate_scopes = {})
  setter = :"#{attribute}="
  original_value = model.send(attribute)

  if case_sensitive.is_a?(Hash)
    alternate_scopes = case_sensitive.merge(alternate_scopes || {})
    case_sensitive = false
  end
  if message.is_a?(Hash)
    alternate_scopes = message.merge(alternate_scopes || {})
    message = nil
  end

  copy = model.dup
  model.save!

  assert_not copy.valid?, message ? (message + ": (#{copy.send(attribute).inspect})") : "Duplicate model with #{attribute}=#{copy.send(attribute).inspect} should not be valid."
  unless case_sensitive
    copy.send(setter, original_value.to_s.upcase)
    assert_not copy.valid?, message ? (message + ": (#{copy.send(attribute).inspect})") : "Duplicate model with #{attribute}=#{copy.send(attribute).inspect} should not be valid."
    copy.send(setter, original_value.to_s.downcase)
    assert_not copy.valid?, message ? (message + ": (#{copy.send(attribute).inspect})") : "Duplicate model with #{attribute}=#{copy.send(attribute).inspect} should not be valid."
  end

  unless alternate_scopes.blank?
    copy.send(setter, original_value)
    assert_not copy.valid?, message ? (message + ": (#{copy.send(attribute).inspect})") : "Duplicate model with #{attribute}=#{copy.send(attribute).inspect} should not be valid."
    alternate_scopes.each do |k,v|
      kset = :"#{k}="
      vorig = copy.send(k)
      copy.send(kset, v)
      assert copy.valid?, message ? (message + ": !#{k}(#{v})") : "Duplicate model with #{k}=#{v.inspect} should be valid with #{attribute}=#{copy.send(attribute).inspect}."
      copy.send(kset, vorig)
      assert_not copy.valid?, message ? (message + ": (#{copy.send(attribute).inspect})") : "Duplicate model with #{attribute}=#{copy.send(attribute).inspect} should not be valid."      end
  end
end
barkest(options = {}) click to toggle source

Installs all known Barkest routes.

Basically every Barkest plugin should define a ‘barkest_’ helper in the routing mapper class, so this method simply finds and executes those helpers.

# File lib/barkest_core/extensions/router_extensions.rb, line 10
def barkest(options = {})
  self.methods.each do |method|
    if method.to_s.index('barkest_') == 0
      send method, options
    end
  end
end
barkest_core(options = {}) click to toggle source

Installs the BarkestCore routes.

# File lib/barkest_core/extensions/router_extensions.rb, line 20
def barkest_core(options = {})
  options = { path: options } if options.is_a?(String)
  options = (options || {}).symbolize_keys

  path = options[:path].blank? ? '/' : options[:path].to_s
  path = '/' + path unless path[0] == '/'

  scope path: path do
    if Rails.env.test? || Rails.env.development?
      scope as: :barkest_core do
        # test routes
        get     'test_access/allow_anon'
        get     'test_access/require_user'
        get     'test_access/require_admin'
        get     'test_access/require_group_x'
        get     'test_report' => 'test_report#index'

        # namespaced tests (these should have the subheader in place).
        get     'test_sub' => 'barkest_core/testsub#page1', as: :testsub_page1
        get     'test_sub/page2' => 'barkest_core/testsub#page2', as: :testsub_page2
        get     'test_sub/page3' => 'barkest_core/testsub#page3', as: :testsub_page3
      end
    end

    # contact form
    get       'contact'                 => 'contact#index'
    post      'contact'                 => 'contact#create'

    # login/logout
    get       'login'                   => 'sessions#new',      as: :login
    post      'login'                   => 'sessions#create'
    delete    'logout'                  => 'sessions#destroy',  as: :logout

    # user management
    get       'signup'                  => 'users#new',         as: :signup
    post      'signup'                  => 'users#create'
    resources :users do
      member do
        get   'disable',                action: :disable_confirm
        patch 'disable',                action: :disable
        put   'disable',                action: :disable
        patch 'enable',                 action: :enable
        put   'enable',                 action: :enable
      end
    end

    # account activation route.
    get       'account_activation/:id'  => 'account_activations#edit', as: 'edit_account_activation'

    # password reset routes.
    resources :password_resets,         only: [:new, :create, :edit, :update]

    # group management
    resources :access_groups

    # status paths
    get       'status/current'          => 'status#current'
    get       'status/first'            => 'status#first'
    get       'status/more'             => 'status#more'
    get       'status/test(/:flag)'     => 'status#test',         as: :status_test

    # system update paths
    get       'system_update/new'
    get       'system_update'           => 'system_update#index', as: :system_update

    # system configuration paths
    get       'system_config'                     => 'system_config#index',               as: :system_config
    post      'system_config/restart'             => 'system_config#restart',             as: :system_config_restart
    SystemConfigController.get_config_items.sort.each do |(item_name, attrib)|
      if attrib[:require_id]
        get     "system_config/#{item_name}/:id"  => "system_config#show_#{item_name}",   as: attrib[:route_name]
        post    "system_config/#{item_name}/:id"  => "system_config#update_#{item_name}"
      else
        get     "system_config/#{item_name}"      => "system_config#show_#{item_name}",   as: attrib[:route_name]
        post    "system_config/#{item_name}"      => "system_config#update_#{item_name}"
      end
    end

    # log view paths
    get         'system_log'            => 'log_view#index', as: :system_log
    post        'system_log'            => 'log_view#index'

  end
end
configure_database() click to toggle source

Generates a database.yml configuration file.

# File lib/generators/barkest_core/actions/08_configure_database.rb, line 4
def configure_database
  config_file = 'config/database.yml'

  attributes = [
      ['adapter', :ask_for_string, %w(sqlite3 postgresql sqlserver mysql2), 'database_adapter'],
      ['pool', :ask_for_int, (1..1000), 'connection_pool_size'],
      ['timeout', :ask_for_int, (500..300000)],
      { 'sqlite3' =>
            [
                [ 'database', :ask_for_string, nil, 'sqlite_database', 'db/my-db.sqlite3' ]
            ],
        'postgresql' =>
            [
                [ 'host', :ask_for_string, nil, 'pg_host' ],
                [ 'port', :ask_for_int, (1..65535), 'pg_port', 5432 ],
                [ 'username', :ask_for_string, nil, 'pg_username' ],
                [ 'password', :ask_for_secret, nil, 'pg_password' ],
                [ 'database', :ask_for_string, nil, 'pg_database' ]
            ],
        'sqlserver' =>
            [
                [ 'host', :ask_for_string, nil, 'sql_host' ],
                [ 'port', :ask_for_int, (1..65535), 'sql_port', 1433 ],
                [ 'username', :ask_for_string, nil, 'sql_username' ],
                [ 'password', :ask_for_secret, nil, 'sql_password' ],
                [ 'database', :ask_for_string, nil, 'sql_database', 'my_db' ]
            ],
        'mysql2' =>
            [
                [ 'host', :ask_for_string, nil, 'mysql_host' ],
                [ 'port', :ask_for_int, (1..65535), 'mysql_port', 3306 ],
                [ 'username', :ask_for_string, nil, 'mysql_username' ],
                [ 'password', :ask_for_secret, nil, 'mysql_password' ],
                [ 'database', :ask_for_string, nil, 'mysql_database', 'my_db' ]
            ]
      }
  ]

  default = {
      'adapter' => 'sqlite3',
      'pool' => 5,
      'timeout' => 5000,
      'database' => 'db/my-data.sqlite3'
  }

  configure_the 'database connection', config_file, attributes, 'adapter', default, 'barkest_core'
end
configure_secrets() click to toggle source

Generates a secrets.yml configuration file.

# File lib/generators/barkest_core/actions/09_configure_secrets.rb, line 5
def configure_secrets
  config_file = 'config/secrets.yml'

  attributes = [
      [ 'recaptcha_public_key', :ask_for_string ],
      [ 'recaptcha_private_key', :ask_for_string ],
      [ 'secret_key_base',  :ask_for_secret_key_base ],
  ]

  default = {}

  configure_the 'application secrets', config_file, attributes, nil, default
end
configure_the(what, config_file, attributes, hash_key, defaults, *optional_levels) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 171
def configure_the(what, config_file, attributes, hash_key, defaults, *optional_levels)
  tell '=' * 79
  if ask_for_bool("Would you like to configure #{what.pluralize}?", true)
    current_config = File.exist?(config_file) ? YAML.load_file(config_file) : {}
    levels = ([''] + (optional_levels || [])).uniq
    levels.each do |level|
      if level.blank? || ask_for_bool("Would you like to configure a #{level} #{what.singularize} configuration?", false)
        current_config[level] ||= {} unless level.blank?

        env_list = %w(test development production)

        unless level.blank?
          level,_,envs = level.partition('[')
          unless envs.blank?
            env_list = envs.partition(']').first.strip.split(' ')
          end
        end

        last_env = nil
        env_list.each do |env|
          tell "Configure the '#{level.blank? ? '' : (level + ':')}#{env}' environment."
          tell '-' * 79

          cur_env = level.blank? ? env : "#{level}_#{env}"

          current = (current_config[cur_env] || {}).dup

          if current.blank? || ask_for_bool("Do you want to make changes to the '#{cur_env}' environment?", false)
            if last_env
              if ask_for_bool("Would you like to use the configuration from the '#{last_env}' environment to start?", false)
                current = (current_config[last_env] || {}).dup
              end
            end
            current = input_attributes(current, attributes, defaults, hash_key)
          end

          current_config[cur_env] = current

          tell '-' * 79

          last_env = cur_env
        end
      end
    end

    perform "> creating '#{config_file}'..." do
      File.write config_file, current_config.to_yaml
    end
  end
end
copy_migrations() click to toggle source

Runs the rake task to install the BarkestCore migrations.

# File lib/generators/barkest_core/actions/07_copy_migrations.rb, line 4
def copy_migrations
  tell '=' * 79
  if ask_for_bool('Would you like to install the BarkestCore database migrations?', true)
    tell 'Copying database migrations...' unless options.quiet?

    ts = Time.now.strftime('%Y%m%d%H%M%S').to_i
    ext = '.barkest_core.rb'

    unless Dir.exist?('db/migrate')
      perform '> creating \'db/migrate\' directory...' do
        Dir.mkdir 'db/migrate'
      end
    end

    existing = Dir.glob("db/migrate/*#{ext}")

    find_existing = Proc.new do |file|
      fn = File.basename(file)[0...-3].partition('_')[2]

      existing.find do |ex|
        fn == File.basename(ex)[0...(-ext.length)].partition('_')[2]
      end
    end

    Dir.glob(File.expand_path('../../../../../db/migrate/*.rb', __FILE__)).each do |file|
      target_file = find_existing.call(file)
      contents = File.read(file)
      if target_file
        cur_contents = File.read(target_file)
        if cur_contents.strip == contents.strip
          tell "> '#{target_file}' is good.", :bright_green
        else
          perform "> updating '#{target_file}'..." do
            File.write target_file, contents
          end
        end
      else
        target_file = "db/migrate/#{ts}_#{File.basename(file)[0...-3].partition('_')[2]}#{ext}"
        ts += 1
        perform "> creating '#{target_file}'..." do
          File.write target_file, contents
        end
      end
    end

  end
end
date() click to toggle source

Gets the date component of the time.

# File lib/barkest_core/extensions/time_extensions.rb, line 134
def date
  return utc.date unless utc?
  Time.utc(year, month, day)
end
erb_read(file) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 8
def erb_read(file)
  ERB.new(File.read(file), 0).result
end
input_attributes(target_hash, attribute_list, defaults, hash_key = nil) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 227
def input_attributes(target_hash, attribute_list, defaults, hash_key = nil)
  attribute_list.each do |data|
    if data.is_a?(Hash)
      if hash_key && target_hash[hash_key] && data[target_hash[hash_key]].is_a?(Array)
        target_hash = input_attributes(target_hash, data[target_hash[hash_key]], defaults, hash_key)
      end
    elsif data.is_a?(Array)
      field,asker,valid,label,default_override = data
      label ||= field
      def_val = if target_hash[field].nil?
                  if default_override.nil?
                    defaults[field]
                  else
                    default_override
                  end
                else
                  target_hash[field]
                end

      answer = if valid
                 send(asker, label, def_val, valid)
               else
                 send(asker, label, def_val)
               end

      target_hash[field] = answer
    end
  end
  target_hash
end
integration_test?() click to toggle source

Are we running an integration test?

# File lib/barkest_core/extensions/test_case_extensions.rb, line 342
def integration_test?
  defined?(post_via_redirect)
end
is_logged_in?() click to toggle source

Is a user currently logged in?

# File lib/barkest_core/extensions/test_case_extensions.rb, line 324
def is_logged_in?
  !session[:user_id].nil?
end
log_in_as(user, options = {}) click to toggle source

Logs in as the specified user.

# File lib/barkest_core/extensions/test_case_extensions.rb, line 330
def log_in_as(user, options = {})
  password      = options[:password]      || 'password'
  remember_me   = options[:remember_me]   || '1'
  if integration_test?
    post login_path, session: { email: user.email, password: password, remember_me: remember_me }
  else
    session[:user_id] = user.id
  end
end
patch_application_controller() click to toggle source

Patches the ApplicationController class to inherit from BarkestCore::ApplicationController.

# File lib/generators/barkest_core/actions/01_patch_application_controller.rb, line 6
  def patch_application_controller
    app_file = 'app/controllers/application_controller.rb'
    dest_source = '::BarkestCore::ApplicationControllerBase'

    if File.exist?(app_file)
      regex = /^(?<NAME>\s*class ApplicationController\s*<\s*)(?<ORIG>\S+)\s*(?<COMMENT>#.*)?$/

      found = false
      changed = false

      lines = File.read(app_file).split("\n").map do |line|
        match = regex.match(line)
        found = true if match
        if match && match['ORIG'] != dest_source
          changed = true
          "#{match['NAME']}#{dest_source} # #{match['ORIG']} #{match['COMMENT']}"
        else
          line
        end
      end

      raise MissingApplicationController.new('ApplicationController class not found') unless found

      if changed
        if ask_for_bool("Your ApplicationController does not currently inherit from BarkestCore.\nWould you like to change this?", true)
          perform "> updating '#{app_file}'..." do
            File.write app_file, lines.join("\n")
          end
        else
          tell "> '#{app_file}' is unchanged.", :bright_green
        end
      else
        tell "> '#{app_file}' is good.", :bright_green
      end

    else

      if ask_for_bool("Your application is missing an ApplicationController.\nWould you like to create one?", true)
        perform "> creating '#{app_file}'..." do
          File.write app_file, <<-APPCTRLR
class ApplicationController < #{dest_source}
  # This is your application controller, it inherits functionality from BarkestCore.
end
          APPCTRLR
        end
      end

    end
  end
patch_application_mailer() click to toggle source

Patches the ApplicationMailer class to inherit from BarkestCore::ApplicationMailer.

# File lib/generators/barkest_core/actions/02_patch_application_mailer.rb, line 7
  def patch_application_mailer
    app_file = 'app/mailers/application_mailer.rb'
    dest_source = '::BarkestCore::ApplicationMailerBase'

    if File.exist?(app_file)
      regex = /^(?<NAME>\s*class ApplicationMailer\s*<\s*)(?<ORIG>\S+)\s*(?<COMMENT>#.*)?$/

      found = false
      changed = false

      lines = File.read(app_file).split("\n").map do |line|
        match = regex.match(line)
        found = true if match
        if match && match['ORIG'] != dest_source
          changed = true
          "#{match['NAME']}#{dest_source} # #{match['ORIG']} #{match['COMMENT']}"
        else
          line
        end
      end

      raise MissingApplicationMailer.new('ApplicationMailer class not found') unless found

      if changed
        if ask_for_bool("Your ApplicationMailer does not currently inherit from BarkestCore.\nWould you like to change this?", true)
          perform "> updating '#{app_file}'..." do
            File.write app_file, lines.join("\n")
          end
        else
          tell "> '#{app_file}' is unchanged.", :bright_green
        end
      else
        tell "> '#{app_file}' is good.", :bright_green
      end

    else

      if ask_for_bool("Your application is missing an ApplicationMailer.\nWould you like to create one?", true)
        perform "> creating '#{app_file}'..." do
          File.write app_file, <<-APPMLR
class ApplicationMailer < #{dest_source}
  # This is your application mailer, it inherits functionality from BarkestCore.
end
          APPMLR
        end
      end

    end
  end
patch_assets() click to toggle source

Patches application.js and application.css

# File lib/generators/barkest_core/actions/03_patch_assets.rb, line 4
def patch_assets
  target = 'barkest_core/application'

  [
      [ 'app/assets/javascripts/application.js', '//=', "// Application.js\n//= require_tree .\n" ],
      [ 'app/assets/stylesheets/application.css', '*=', "/*\n * Application.css\n *= require_tree .\n *= require_self\n */\n" ]
  ].each do |(path, line_tag, def_contents)|

    lines = if File.exist?(path)
                 File.read(path)
               else
                 def_contents
               end.split("\n")

    first_tag = -1
    first_pass = true
    loop do
      lines.each_with_index do |line, index|
        if line.strip[0...line_tag.length] == line_tag
          first_tag = index
          break
        end
      end
      if first_tag < 0
        if first_pass
          lines += def_contents.split("\n")
        else
          raise StandardError.new("Failed to locate line starting with '#{line_tag}' in '#{path}'.")
        end
      else
        break
      end
      first_pass = false
    end

    regex = /^\s*#{line_tag.gsub('*',"\\*")}\s*require\s*['"]?#{target}['"]?\s*$/

    tag_index = -1
    lines.each_with_index do |line, index|
      if regex.match(line)
        tag_index = index
        break
      end
    end
    if tag_index < 0
      if ask_for_bool("Would you like to add a reference to BarkestCore in '#{path}'?", true)
        lines.insert first_tag, "#{line_tag} require #{target}"
        perform "> updating '#{path}'..." do
          File.write path, lines.join("\n")
        end
      else
        tell "> '#{path}' is unchanged.", :bright_green
      end
    else
      tell "> '#{path}' is good.", :bright_green
    end
  end
end
patch_gitignore() click to toggle source

Patches .gitignore to ignore *.yml configuration files.

# File lib/generators/barkest_core/actions/99_patch_gitignore.rb, line 4
def patch_gitignore
  file = '.gitignore'

  dummy = Dir.exist?('test/dummy')

  if File.exist?(file)
    lines = File.read(file).split("\n")

    protect_cfg = false
    protect_dummy_cfg = false

    cfg_regex = /^\s*config\/\*.yml\s*$/
    dummy_cfg_regex = /^\s*test\/dummy\/config\/\*.yml\s*$/

    lines.each do |line|
      protect_cfg = true if cfg_regex.match(line)
      protect_dummy_cfg = true if dummy_cfg_regex.match(line)
    end

    changed = false
    unless protect_cfg
      if ask_for_bool("Your .gitignore does not protect your YAML configuration files.\nWould you like to add a line to protect them?", true)
        lines << 'config/*.yml'
        changed = true
      end
    end

    if dummy && !protect_dummy_cfg
      if ask_for_bool("Your .gitignore does not protect your dummy application's YAML configuration files.\nWould you like to add a line to protect them?", true)
        lines << 'test/dummy/config/*.yml'
        changed = true
      end
    end

    if changed
      perform '> updating \'.gitignore\'...' do
        File.write file, lines.join("\n")
      end
    else
      tell '> \'.gitignore\' is good.', :bright_green
    end

  else
    if ask_for_bool('Would you like to create a .gitignore that protects your YAML files?', true)
      perform '> creating \'.gitignore\'...' do
        contents = %w(.bundle/ .sass-cache/ config/*.yml db/*.sqlite3 db/*.sqlite3-journal doc/ log/*.log pkg/ tmp/ vendor/bundle/)
        contents += contents.map{|v| "test/dummy/#{v}"} if dummy

        File.write file, contents.join("\n")
      end
    end
  end
end
patch_layouts() click to toggle source

Patches the default layouts to inherit from the BarkestCore layouts.

# File lib/generators/barkest_core/actions/04_patch_layouts.rb, line 4
def patch_layouts
  {
      'app/views/layouts/application.html.erb' => 'layouts/barkest_core/application',
      'app/views/layouts/mailer.html.erb' => 'layouts/barkest_core/html_mailer',
      'app/views/layouts/mailer.text.erb' => 'layouts/barkest_core/text_mailer'
  }.each do |file,layout|

    if File.exist?(file)
      regex = /<%=\s+render[\s\(]+['"]#{layout}['"][\)\s]*%>/
      if regex.match(File.read(file))
        tell "> '#{file}' is good.", :bright_green
      else
        if ask_for_bool("Your '#{file}' layout does not reference the BarkestCore layout.\nWould you like to change it to use the BarkestCore layout?", true)
          perform "> updating '#{file}'..." do
            File.write file, "<%= render '#{layout}' %>\n"
          end
        else
          tell "> '#{file}' is unchanged.", :bright_green
        end
      end
    else
      if ask_for_bool("Your application is missing '#{file}'.\nWould you like to add one?", true)
        perform "> creating '#{file}'..." do
          File.write file, "<%= render '#{layout}' %>\n"
        end
      else
        tell "> '#{file}' is missing.", :yellow
      end
    end

  end
end
patch_routes() click to toggle source

Patches routes.rb to include BarkestCore::Engine and session management paths.

# File lib/generators/barkest_core/actions/05_patch_routes.rb, line 4
  def patch_routes
    route_file = 'config/routes.rb'

    unless File.exist?(route_file)

      if ask_for_bool("Your application is missing a 'routes.rb' configuration file.\nWould you like to create one?", true)
        perform '> creating \'routes.rb\'...' do
          File.write route_file, <<-DEFRTS
Rails.application.routes.draw do
  # Enter your routes in this file.
end
          DEFRTS
        end
      else
        tell '> missing \'routes.rb\'.', :yellow
        return
      end

    end

    lines = File.exist?(route_file) ? File.read(route_file).split("\n") : ['Rails.application.routes.draw do','end']

    insert_at = -1

    regex = /.*\.routes\.draw\s+do\s*(#.*)?$/
    lines.each_with_index do |line, index|
      if regex.match(line)
        insert_at = index + 1
        break
      end
    end

    raise MissingRoutes.new('routes not found') unless insert_at >= 0

    changed = false

    core_regex = /^\s*barkest_core([\s\(]+(?<OPTIONS>[^\s\)#][^\)#]+)\)?)?\s*(?<COMMENT>#.*)?$/
    root_regex = /^\s*root([\s\(]+(?<OPTIONS>[^\s\)#][^\)#]+)\)?)?\s*(?<COMMENT>#.*)?$/
    core = nil
    root = nil

    lines.each_with_index do |line, index|
      line = line.strip
      if (match = core_regex.match(line))
        opts = match['OPTIONS'].to_s.strip
        core = {
            index: index,
            path: if (path_offset = opts.index(':path'))
                    opts[path_offset..-1].partition('=>')[2].partition(',')[0].strip[1...-1]
                  elsif (path_offset = opts.index('path:'))
                    opts[path_offset..-1].partition(':')[2].partition(',')[0].strip[1...-1]
                  else
                    opts[1...-1]  # strip '...' or "..." to ...
                  end.to_s
        }
      elsif (match = root_regex.match(line))
        opts = match['OPTIONS'].to_s.strip
        root = {
            index: index,
            path: opts[1...-1].to_s # strip '...' or "..." to ...
        }
      end
    end

    unless core
      if ask_for_bool('Would you like to add the \'barkest_core\' routes to your application?', true)
        path = ask_for_string('What path prefix would you like?', '/')
        lines.insert insert_at, "\n  barkest_core #{path.inspect}"
        changed = true
      end
    end

    unless root
      if ask_for_bool("Your application is missing a root route.\nWould you like to add one?", true)
        path = ask_for_string('What controller#action would you like for your root route?', 'test_access#allow_anon')
        lines.insert insert_at, "\n  root #{path.inspect}"
        changed = true
      end
    end

    if changed
      perform '> updating \'routes.rb\'...' do
        File.write route_file, lines.join("\n")
      end
    else
      tell '> \'routes.rb\' is good.', :bright_green
    end

  end
patch_seeds() click to toggle source

Patches the db/seeds to allow multiple seeding files and includes the seeds necessary for BarkestCore.

# File lib/generators/barkest_core/actions/06_patch_seeds.rb, line 4
def patch_seeds
  files = Dir.glob(File.expand_path('../../../../../db/{seeds.rb,seeds/*.rb}', __FILE__))

  unless Dir.exists?('db/seeds')
    if ask_for_bool("Your application does not currently have a 'db/seeds' directory.\nBarkestCore can alter your application to make use of multiple seeding files.\nDo you want to create the 'db/seeds' directory to enable this behavior?", true)
      perform '> creating \'db/seeds\' directory.' do
        Dir.mkdir 'db' unless Dir.exists?('db')
        Dir.mkdir 'db/seeds'
      end
    else
      tell "> 'db/seeds' directory is missing.", :yellow
      return
    end
  end

  source = files.find{|v| v[-11..-1] == 'db/seeds.rb'}
  prefix_dir_len = source.length - 11
  dest = source[prefix_dir_len..-1]

  if File.exist?(dest)
    current_contents = File.read(dest).strip

    # if 'seeds.rb' defines a 'Seeds' class, then we assuming it to be a seeder.
    is_seeder = false
    current_contents.split("\n").each do |line|
      is_seeder = true if /^\s*class\s+Seeds(\s.*)?$/.match(line)
    end

    unless is_seeder
      if ask_for_bool('Would you like to move your \'db/seeds.rb\' file into the \'db/seeds\' directory?', true)
        perform "> moving '#{dest}' into 'db/seeds' directory..." do
          File.rename dest, 'db/seeds/' + File.basename(dest)
        end
      else
        tell "> 'db/seeds.rb' is not being moved.", :yellow
      end
    end
  end

  files.each do |source|
    dest = source[prefix_dir_len..-1]
    contents = File.read(source)

    if File.exist?(dest) && File.read(dest).strip == contents.strip
      tell "> '#{dest}' is good.", :bright_green
    else
      if !File.exist?(dest) || ask_for_bool("Would you like to update '#{dest}'?", true)
        perform "> #{File.exist?(dest) ? 'creating' : 'updating'} \'#{dest}\'..." do
          File.write dest, contents
        end
      else
        tell "> '#{dest}' is unchanged.", :bright_green
      end
    end
  end
end
perform(message, &block) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 222
def perform(message, &block)
  tell message + (options.pretend? ? ' [pretend]' : ''), :teal
  block.call unless options.pretend?
end
running?() click to toggle source

Is the rails server running?

# File lib/barkest_core/extensions/application_extensions.rb, line 6
def running?
  path = File.join(Rails.root, 'tmp/pids/server.pid')
  pid = File.exist?(path) ? File.read(path).to_i : -1
  server_running = true
  begin
    Process.getpgid pid
  rescue Errno::ESRCH
    server_running = false
  end
  server_running
end
tell(message, color = nil) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 12
def tell(message, color = nil)
  open = ''
  close = ''
  if color
    open = "\033[0"
    close = "\033[0m"
    open += case color.to_sym
              when :black
                ';30'
              when :dark_grey, :dark_gray
                ';30;1'
              when :red, :dark_red
                ';31'
              when :bright_red
                ';31;1'
              when :green, :dark_green
                ';32'
              when :bright_green
                ';32;1'
              when :gold, :dark_yellow
                ';33'
              when :yellow
                ';33;1'
              when :dark_blue
                ';34'
              when :blue
                ';34;1'
              when :dark_magenta, :violet, :purple
                ';35'
              when :magenta, :pink
                ';35;1'
              when :dark_cyan, :teal
                ';36'
              when :cyan, :aqua
                ';36;1'
              when :light_gray, :gray, :light_grey, :grey
                ';37'
              when :white
                ';37;1'
              when :bold
                ';1'
              else
                ''
            end + 'm'

  end

  puts open + message + close unless options.quiet?
end
trimq(question) click to toggle source
# File lib/barkest_core/extensions/generator_extensions.rb, line 261
def trimq(question)
  question = question.to_s.strip
  if question[-1] == '?' || question[-1] == ':'
    question = question[0...-1]
  end
  question
end