Skip to content

Instantly share code, notes, and snippets.

@bvandgrift
Created February 28, 2016 03:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bvandgrift/ce9464b8eb1c141dd608 to your computer and use it in GitHub Desktop.
Save bvandgrift/ce9464b8eb1c141dd608 to your computer and use it in GitHub Desktop.
a terrible way to determine whether attrs on an ActiveRecord model are optional or required when using grape.
module AttributeRequirements
extend ActiveSupport::Concern
SQL_TYPE_MATCHERS = {
/^character varying/ => String,
/^text/ => String,
/^enum/ => String,
/^timestamp/ => DateTime,
/^integer/ => Integer,
/^numeric/ => BigDecimal,
/^boolean/ => Virtus::Attribute::Boolean
}.freeze
# this should apply to active record objects only
included do
class << self
def build_column_type_mapping
begin
column_type_mapping = self.columns.inject({}) { |r,c| r[c.name] = c.sql_type; r }
rescue ActiveRecord::StatementInvalid => ex
Rails.logger.warn "Columns not defined: #{ex.message}"
return {}
end
enum_keys = defined_enums.keys
column_type_mapping.reduce({}) do |r, e|
k,v = e
r[k] = (enum_keys.include?(k) ? "enum" : v)
r
end
end
def column_type_mapping
@columns_types_memo ||= build_column_type_mapping
end
def attribute_keys
@columns_keys_memo ||= column_type_mapping.symbolize_keys.keys
end
def build_required_keys
ks = self.validators.select { |v| v.is_a? ActiveRecord::Validations::PresenceValidator }
.select { |v| !v.options.keys.include? :if}
.map(&:attributes).flatten.uniq.freeze
ks - [:id]
end
def required_attribute_keys
@req_attr_keys ||= build_required_keys.map(&:to_s).freeze
end
def optional_attribute_keys
@opt_attr_keys ||= (attribute_keys - required_attribute_keys).map(&:to_s).freeze
end
def coercion_for(k)
SQL_TYPE_MATCHERS.detect { |m,v| k.match(m) }.last
end
end
def required_attributes
enum_filter(attributes.slice(*self.class.required_attribute_keys))
end
def enum_filter(att_hash)
de = defined_enums.keys
att_hash.reduce({}) do |r,e|
k,v = e
r[k] = (de.include?(k) ? self.send(k.to_sym) : v)
r
end
end
end
end
module ParamScopeHelper
Grape::Validations::ParamsScope.class_eval do
def params_for(model, opts = {})
mp = model.column_type_mapping.inject({}) { |r,v| r[v.first] = model.coercion_for(v.last); r }
if opts[:all_optional]
optional_params_for(mp)
else
required_params_for(mp.slice(*model.required_attribute_keys))
optional_params_for(mp.slice(*model.optional_attribute_keys))
end
end
def required_params_for(mapping)
mapping.each do |k,v|
requires k, type: v
end
end
def optional_params_for(mapping)
mapping.each do |k,v|
optional k, type: v
end
end
end
end
module StrongParamsHelpers
extend Grape::API::Helpers
def permitted_params
@permitted_params ||= declared(params, include_missing: false, include_parent_namespaces: false)
end
end
module API
module V1
class Resource < Grape::API
version 'v1'
format :json
helpers StrongParamsHelpers
helpers ParamScopeHelper
resource :things do
# .. other stuff
desc "Create an Thing"
params do
requires :thing, type: Hash do |scope|
params_for(Thing) # magic!
end
end
post do
thing = ::Thing.new(permitted_params[:thing])
error!({message: "invalid",
params: params,
errors: thing.errors.messages},
422) unless (thing.valid? && thing.save!)
serialize_model(thing)
end
# more thing stuff
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment