“...I've been working since 2008 with Ruby / Ruby on Rails, love a bit of Elixir / Phoenix and learning Rust. I also poke through other people's code and make PRs for OpenSource Ruby projects that sometimes make it. Currently working for InPay...”

Rob Lacey (contact@robl.me)
Senior Software Engineer, Brighton, UK

Write code with code so you don't have to write code

I’m currently trying to bring in Strong Parameters, replacing attr_accessible with a custom class for each model class we want to sanitize params for. We don’t want to fill controllers with massive blah_params methods if we’re likely to re-use them everywhere. So if we hand this over to a class that can manage it….all the better.

BlahPermit.with(param.fetch(:blah, {})).permit

I’d rather not go through every file and create an accompanying class for what, at this stage, will only require a small tweak to each attr_accessible call.

So here we can cope with both classes we can load or define on the fly if they don’t already exist.

module NotAtAllAttrAccessible
  extend ActiveSupport::Concern

  module ClassMethods
    def attr_accessible(*attrs)
      klass = define_permit_class
      klass.permits += attrs

      define_method :accessible_attributes do
        klass.permits
      end unless method_defined? :accessible_attributes
    end

    private

    def define_permit_class
      name = "#{self.name}Permit"
      Object.const_get(name)

    rescue NameError
      Object.const_set(name, Class.new(Permit))
    end
  end
end

And that will equate to

class Blah
  attr_accessible :thing, :the, :blair, :turnip
end

class Permit
  cattr_accessor :permits, default: []

  class << self
    def klass(klass)
      "#{klass}Permit".constantize
    rescue NameError
      Permit
    end

    def with(params)
      new.with(params)
    end
  end

  def with(params)
    @params = params.is_a?(ActionController::Parameters) ? params : ActionController::Parameters.new(params)
    self
  end

  def permit!
    permits.any? ? params.permit(*permits) : params.permit!
  end

  def permits
    self.class.permits
  end

  def params
    @params || ActionController::Parameters.new
  end
end

class BlahPermit < Permit
  @@permits = [:thing, :the, :blair, :turnip]
end

And we can then crush Parameters like a boss.

def resource_params
  Permit.klass(Blah).with(param.fetch(:blah, {})).permit
end