Skip to content

Instantly share code, notes, and snippets.

@estum
Created October 23, 2020 10:23
Show Gist options
  • Save estum/04cd73efc353017e5cbd9ba00b5d1363 to your computer and use it in GitHub Desktop.
Save estum/04cd73efc353017e5cbd9ba00b5d1363 to your computer and use it in GitHub Desktop.
ActiveRecord's Insert Statement Generator
# frozen_string_literal: true
require "dry-initializer"
# Simply generates insert statement with optional overridings.
# === Usage:
#
# like = Like.last
# # => #<Like:0x00007f8a42b07858> {
# # :id => "f93c47dc-9e09-4bf4-bbcb-2ec2000a873f",
# # :post_id => "e70059b5-983c-4fb6-9aba-b5462c0192d4",
# # :user_id => 34,
# # :deleted_at => nil,
# # :created_at => Tue, 10 Dec 2019 17:30:45 MSK +03:00,
# # :updated_at => Tue, 10 Dec 2019 17:30:45 MSK +03:00
# # }
# puts InsertStatementGenerator[like, user_id: 1]
# # => INSERT INTO "likes" ("id", "post_id", "user_id", "deleted_at", "created_at", "updated_at")
# # VALUES ('f93c47dc-9e09-4bf4-bbcb-2ec2000a873f', 'e70059b5-983c-4fb6-9aba-b5462c0192d4', 1, NULL, '2019-12-10 14:30:45.348052', '2019-12-10 14:30:45.348052')
class InsertStatementGenerator
extend Dry::Initializer
class << self
# :call-seq:
# InsertStatementGenerator[record, **override] → String
# InsertStatementGenerator.(record, **override) → String
# InsertStatementGenerator.call(record, **override) → String
#
# Generates a finel SQL +INSERT+ statement for the given record
# and overrided values.
def call(record, **override)
new(record, override).to_sql
end
alias_method :[], :call
end
param :record, Types.Instance(ActiveRecord::Base)
param :override, optional: true, default: -> { {} }
# Returns Arel::InsertManager with a final statement
def manager
arel_table.compile_insert(values_for_insert)
end
# Returns a final SQL statement
delegate :to_sql, to: :manager
# Generates an associative array of column-value pairs with overrided values
def values_for_insert
record_attributes.map { |col, val| column_value(col, val) }
end
# Invokes +attributes_with_values_for_create+ for the record with it's attribute names
def record_attributes
record.send(:attributes_with_values_for_create, record.attribute_names)
end
private
# Returns a single column-value pair with overrided value.
def column_value(name, value)
arel = arel_attribute(name)
value = @override.fetch(name.to_sym, value)
[arel, type_cast(arel, value)]
end
# Type casts value for a given column
def type_cast(arel_attr, value)
arel_attr.type_cast_for_database(value)
end
delegate :arel_table, :arel_attribute, to: "record.class"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment