Move Attribute and AttributeSet to ActiveModel

Use these to back the attributes API.  Stop automatically including
ActiveModel::Dirty in ActiveModel::Attributes, and make it optional.
This commit is contained in:
Lisa Ugray 2017-10-19 12:45:07 -04:00
parent dac7c8844b
commit c3675f50d2
22 changed files with 227 additions and 238 deletions

View File

@ -30,6 +30,8 @@ require "active_model/version"
module ActiveModel
extend ActiveSupport::Autoload
autoload :Attribute
autoload :Attributes
autoload :AttributeAssignment
autoload :AttributeMethods
autoload :BlockValidator, "active_model/validator"

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module ActiveRecord
module ActiveModel
class Attribute # :nodoc:
class << self
def from_database(name, value, type)
@ -130,8 +130,6 @@ module ActiveRecord
coder["value"] = value if defined?(@value)
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :original_attribute
@ -237,6 +235,7 @@ module ActiveRecord
self.class.new(name, type)
end
end
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true
require "active_record/attribute"
require "active_model/attribute"
module ActiveRecord
module ActiveModel
class Attribute # :nodoc:
class UserProvidedDefault < FromUser # :nodoc:
def initialize(name, value, type, database_default)
@ -22,8 +22,6 @@ module ActiveRecord
self.class.new(name, user_provided_value, type, original_attribute)
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :user_provided_value

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module ActiveRecord
module ActiveModel
class AttributeMutationTracker # :nodoc:
OPTION_NOT_GIVEN = Object.new
@ -107,5 +107,8 @@ module ActiveRecord
def original_value(*)
end
def force_change(*)
end
end
end

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true
require "active_record/attribute_set/builder"
require "active_record/attribute_set/yaml_encoder"
require "active_model/attribute_set/builder"
require "active_model/attribute_set/yaml_encoder"
module ActiveRecord
module ActiveModel
class AttributeSet # :nodoc:
delegate :each_value, :fetch, to: :attributes

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true
require "active_record/attribute"
require "active_model/attribute"
module ActiveRecord
module ActiveModel
class AttributeSet # :nodoc:
class Builder # :nodoc:
attr_reader :types, :always_initialized, :default
@ -92,8 +92,6 @@ module ActiveRecord
@materialized = true
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :types, :values, :additional_types, :delegate_hash, :default

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true
module ActiveRecord
module ActiveModel
class AttributeSet
# Attempts to do more intelligent YAML dumping of an
# ActiveRecord::AttributeSet to reduce the size of the resulting string
# ActiveModel::AttributeSet to reduce the size of the resulting string
class YAMLEncoder # :nodoc:
def initialize(default_types)
@default_types = default_types
@ -33,8 +33,6 @@ module ActiveRecord
end
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :default_types

View File

@ -1,24 +1,30 @@
# frozen_string_literal: true
require "active_support/core_ext/object/deep_dup"
require "active_model/type"
require "active_model/attribute_set"
require "active_model/attribute/user_provided_default"
module ActiveModel
module Attributes #:nodoc:
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
include ActiveModel::Dirty
included do
attribute_method_suffix "="
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
self.attribute_types = {}
self._default_attributes = {}
self.attribute_types = Hash.new(Type.default_value)
self._default_attributes = AttributeSet.new({})
end
module ClassMethods
def attribute(name, cast_type = Type::Value.new, **options)
self.attribute_types = attribute_types.merge(name.to_s => cast_type)
self._default_attributes = _default_attributes.merge(name.to_s => options[:default])
def attribute(name, type = Type::Value.new, **options)
name = name.to_s
if type.is_a?(Symbol)
type = ActiveModel::Type.lookup(type, **options.except(:default))
end
self.attribute_types = attribute_types.merge(name => type)
define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
define_attribute_methods(name)
end
@ -37,11 +43,29 @@ module ActiveModel
undef_method :__temp__#{safe_name}=
STR
end
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
private_constant :NO_DEFAULT_PROVIDED
def define_default_attribute(name, value, type)
self._default_attributes = _default_attributes.deep_dup
if value == NO_DEFAULT_PROVIDED
default_attribute = _default_attributes[name].with_type(type)
else
default_attribute = Attribute::UserProvidedDefault.new(
name,
value,
type,
_default_attributes.fetch(name.to_s) { nil },
)
end
_default_attributes[name] = default_attribute
end
end
def initialize(*)
@attributes = self.class._default_attributes.deep_dup
super
clear_changes_information
end
private
@ -53,21 +77,17 @@ module ActiveModel
attr_name.to_s
end
cast_type = self.class.attribute_types[name]
deserialized_value = ActiveModel::Type.lookup(cast_type).cast(value)
attribute_will_change!(name) unless deserialized_value == attribute(name)
instance_variable_set("@#{name}", deserialized_value)
deserialized_value
@attributes.write_from_user(attr_name.to_s, value)
value
end
def attribute(name)
if instance_variable_defined?("@#{name}")
instance_variable_get("@#{name}")
def attribute(attr_name)
name = if self.class.attribute_alias?(attr_name)
self.class.attribute_alias(attr_name).to_s
else
default = self.class._default_attributes[name]
default.respond_to?(:call) ? default.call : default
attr_name.to_s
end
@attributes.fetch_value(name)
end
# Handle *= for method_missing.

View File

@ -2,6 +2,7 @@
require "active_support/hash_with_indifferent_access"
require "active_support/core_ext/object/duplicable"
require "active_model/attribute_mutation_tracker"
module ActiveModel
# == Active \Model \Dirty
@ -130,6 +131,24 @@ module ActiveModel
attribute_method_affix prefix: "restore_", suffix: "!"
end
def initialize_dup(other) # :nodoc:
super
if self.class.respond_to?(:_default_attributes)
@attributes = self.class._default_attributes.map do |attr|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
end
end
@mutations_from_database = nil
end
def changes_applied # :nodoc:
@previously_changed = changes
@mutations_before_last_save = mutations_from_database
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
# Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
#
# person.changed? # => false
@ -148,36 +167,6 @@ module ActiveModel
changed_attributes.keys
end
# Returns a hash of changed attributes indicating their original
# and new values like <tt>attr => [original value, new value]</tt>.
#
# person.changes # => {}
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
def changes
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
end
# Returns a hash of attributes that were changed before the model was saved.
#
# person.name # => "bob"
# person.name = 'robert'
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
end
# Returns a hash of the attributes with unsaved changes indicating their original
# values like <tt>attr => original value</tt>.
#
# person.name # => "bob"
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def changed_attributes
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
end
# Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
!!changes_include?(attr) &&
@ -200,11 +189,103 @@ module ActiveModel
attributes.each { |attr| restore_attribute! attr }
end
# Clears all dirty data: current changes and previous changes.
def clear_changes_information
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
def clear_attribute_changes(attr_names)
attributes_changed_by_setter.except!(*attr_names)
attr_names.each do |attr_name|
clear_attribute_change(attr_name)
end
end
# Returns a hash of the attributes with unsaved changes indicating their original
# values like <tt>attr => original value</tt>.
#
# person.name # => "bob"
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def changed_attributes
# This should only be set by methods which will call changed_attributes
# multiple times when it is known that the computed value cannot change.
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
end
end
# Returns a hash of changed attributes indicating their original
# and new values like <tt>attr => [original value, new value]</tt>.
#
# person.changes # => {}
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
def changes
cache_changed_attributes do
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
end
end
# Returns a hash of attributes that were changed before the model was saved.
#
# person.name # => "bob"
# person.name = 'robert'
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
@previously_changed.merge(mutations_before_last_save.changes)
end
def attribute_changed_in_place?(attr_name) # :nodoc:
mutations_from_database.changed_in_place?(attr_name)
end
private
def clear_attribute_change(attr_name)
mutations_from_database.forget_change(attr_name)
end
def mutations_from_database
unless defined?(@mutations_from_database)
@mutations_from_database = nil
end
@mutations_from_database ||= if @attributes
ActiveModel::AttributeMutationTracker.new(@attributes)
else
NullMutationTracker.instance
end
end
def forget_attribute_assignments
@attributes = @attributes.map(&:forgetting_assignment) if @attributes
end
def mutations_before_last_save
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
end
def cache_changed_attributes
@cached_changed_attributes = changed_attributes
yield
ensure
clear_changed_attributes_cache
end
def clear_changed_attributes_cache
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
end
# Returns +true+ if attr_name is changed, +false+ otherwise.
def changes_include?(attr_name)
attributes_changed_by_setter.include?(attr_name)
attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
end
alias attribute_changed_by_setter? changes_include?
@ -214,18 +295,6 @@ module ActiveModel
previous_changes.include?(attr_name)
end
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied # :doc:
@previously_changed = changes
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
# Clears all dirty data: current changes and previous changes.
def clear_changes_information # :doc:
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
# Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
@ -238,15 +307,16 @@ module ActiveModel
# Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
unless attribute_changed?(attr)
begin
value = _read_attribute(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
begin
value = _read_attribute(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
set_attribute_was(attr, value)
end
set_attribute_was(attr, value)
mutations_from_database.force_change(attr)
end
# Handles <tt>restore_*!</tt> for +method_missing+.
@ -257,18 +327,13 @@ module ActiveModel
end
end
# This is necessary because `changed_attributes` might be overridden in
# other implementations (e.g. in `ActiveRecord`)
alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
def attributes_changed_by_setter
@attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
end
# Force an attribute to have a particular "before" value
def set_attribute_was(attr, old_value)
attributes_changed_by_setter[attr] = old_value
end
# Remove changes information for the provided attributes.
def clear_attribute_changes(attributes) # :doc:
attributes_changed_by_setter.except!(*attributes)
end
end
end

View File

@ -32,6 +32,10 @@ module ActiveModel
def lookup(*args, **kwargs) # :nodoc:
registry.lookup(*args, **kwargs)
end
def default_value # :nodoc:
@default_value ||= Value.new
end
end
register(:big_integer, Type::BigInteger)

View File

@ -2,8 +2,8 @@
require "cases/helper"
module ActiveRecord
class AttributeSetTest < ActiveRecord::TestCase
module ActiveModel
class AttributeSetTest < ActiveModel::TestCase
test "building a new set from raw attributes" do
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
attributes = builder.build_from_database(foo: "1.1", bar: "2.2")

View File

@ -2,8 +2,8 @@
require "cases/helper"
module ActiveRecord
class AttributeTest < ActiveRecord::TestCase
module ActiveModel
class AttributeTest < ActiveModel::TestCase
setup do
@type = Minitest::Mock.new
end

View File

@ -1,12 +1,12 @@
# frozen_string_literal: true
require "cases/helper"
require "active_model/attributes"
class AttributesDirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Dirty
attribute :name, :string
attribute :color, :string
attribute :size, :integer
@ -69,12 +69,10 @@ class AttributesDirtyTest < ActiveModel::TestCase
end
test "attribute mutation" do
@model.instance_variable_set("@name", "Yam".dup)
@model.name = "Yam"
@model.save
assert !@model.name_changed?
@model.name.replace("Hadad")
assert !@model.name_changed?
@model.name_will_change!
@model.name.replace("Baal")
assert @model.name_changed?
end
@ -190,4 +188,18 @@ class AttributesDirtyTest < ActiveModel::TestCase
assert_equal "Dmitry", @model.name
assert_equal "White", @model.color
end
test "changing the attribute reports a change only when the cast value changes" do
@model.size = "2.3"
@model.save
@model.size = "2.1"
assert_equal false, @model.changed?
@model.size = "5.1"
assert_equal true, @model.changed?
assert_equal true, @model.size_changed?
assert_equal({ "size" => [2, 5] }, @model.changes)
end
end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
require "active_model/attributes"
module ActiveModel
class AttributesTest < ActiveModel::TestCase
@ -13,7 +12,7 @@ module ActiveModel
attribute :string_field, :string
attribute :decimal_field, :decimal
attribute :string_with_default, :string, default: "default string"
attribute :date_field, :string, default: -> { Date.new(2016, 1, 1) }
attribute :date_field, :date, default: -> { Date.new(2016, 1, 1) }
attribute :boolean_field, :boolean
end
@ -48,31 +47,6 @@ module ActiveModel
assert_equal true, data.boolean_field
end
test "dirty" do
data = ModelForAttributesTest.new(
integer_field: "2.3",
string_field: "Rails FTW",
decimal_field: "12.3",
boolean_field: "0"
)
assert_equal false, data.changed?
data.integer_field = "2.1"
assert_equal false, data.changed?
data.string_with_default = "default string"
assert_equal false, data.changed?
data.integer_field = "5.1"
assert_equal true, data.changed?
assert_equal true, data.integer_field_changed?
assert_equal({ "integer_field" => [2, 5] }, data.changes)
end
test "nonexistent attribute" do
assert_raise ActiveModel::UnknownAttributeError do
ModelForAttributesTest.new(nonexistent: "nonexistent")

View File

@ -219,4 +219,8 @@ class DirtyTest < ActiveModel::TestCase
assert_equal "Dmitry", @model.name
assert_equal "White", @model.color
end
test "model can be dup-ed without Attributes" do
assert @model.dup
end
end

View File

@ -27,14 +27,14 @@ require "active_support"
require "active_support/rails"
require "active_model"
require "arel"
require "yaml"
require "active_record/version"
require "active_record/attribute_set"
require "active_model/attribute_set"
module ActiveRecord
extend ActiveSupport::Autoload
autoload :Attribute
autoload :Base
autoload :Callbacks
autoload :Core
@ -181,3 +181,7 @@ end
ActiveSupport.on_load(:i18n) do
I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
end
YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
require "active_record/attribute_mutation_tracker"
module ActiveRecord
module AttributeMethods
@ -33,65 +32,13 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
@mutations_from_database = nil
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
end
def initialize_dup(other) # :nodoc:
super
@attributes = self.class._default_attributes.map do |attr|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
end
@mutations_from_database = nil
end
def changes_applied # :nodoc:
@mutations_before_last_save = mutations_from_database
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
def clear_changes_information # :nodoc:
@mutations_before_last_save = nil
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
def clear_attribute_changes(attr_names) # :nodoc:
super
attr_names.each do |attr_name|
clear_attribute_change(attr_name)
end
end
def changed_attributes # :nodoc:
# This should only be set by methods which will call changed_attributes
# multiple times when it is known that the computed value cannot change.
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
super.reverse_merge(mutations_from_database.changed_values).freeze
end
end
def changes # :nodoc:
cache_changed_attributes do
super
end
end
def previous_changes # :nodoc:
mutations_before_last_save.changes
end
def attribute_changed_in_place?(attr_name) # :nodoc:
mutations_from_database.changed_in_place?(attr_name)
end
# Did this attribute change when we last saved? This method can be invoked
# as +saved_change_to_name?+ instead of <tt>saved_change_to_attribute?("name")</tt>.
# Behaves similarly to +attribute_changed?+. This method is useful in
@ -182,26 +129,6 @@ module ActiveRecord
result
end
def mutations_from_database
unless defined?(@mutations_from_database)
@mutations_from_database = nil
end
@mutations_from_database ||= AttributeMutationTracker.new(@attributes)
end
def changes_include?(attr_name)
super || mutations_from_database.changed?(attr_name)
end
def clear_attribute_change(attr_name)
mutations_from_database.forget_change(attr_name)
end
def attribute_will_change!(attr_name)
super
mutations_from_database.force_change(attr_name)
end
def _update_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
@ -213,25 +140,6 @@ module ActiveRecord
def keys_for_partial_write
changed_attribute_names_to_save & self.class.column_names
end
def forget_attribute_assignments
@attributes = @attributes.map(&:forgetting_assignment)
end
def mutations_before_last_save
@mutations_before_last_save ||= NullMutationTracker.instance
end
def cache_changed_attributes
@cached_changed_attributes = changed_attributes
yield
ensure
clear_changed_attributes_cache
end
def clear_changed_attributes_cache
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
end
end
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require "active_record/attribute/user_provided_default"
require "active_model/attribute/user_provided_default"
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
@ -250,14 +250,14 @@ module ActiveRecord
if value == NO_DEFAULT_PROVIDED
default_attribute = _default_attributes[name].with_type(type)
elsif from_user
default_attribute = Attribute::UserProvidedDefault.new(
default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
name,
value,
type,
_default_attributes.fetch(name.to_s) { nil },
)
else
default_attribute = Attribute.from_database(name, value, type)
default_attribute = ActiveModel::Attribute.from_database(name, value, type)
end
_default_attributes[name] = default_attribute
end

View File

@ -8,7 +8,7 @@ module ActiveRecord
case coder["active_record_yaml_version"]
when 1, 2 then coder
else
if coder["attributes"].is_a?(AttributeSet)
if coder["attributes"].is_a?(ActiveModel::AttributeSet)
Rails420.convert(klass, coder)
else
Rails41.convert(klass, coder)

View File

@ -323,7 +323,7 @@ module ActiveRecord
end
def attributes_builder # :nodoc:
@attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name|
@attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name|
unless columns_hash.key?(name)
_default_attributes[name].dup
end
@ -346,7 +346,7 @@ module ActiveRecord
end
def yaml_encoder # :nodoc:
@yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types)
@yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
end
# Returns the type of the attribute with the given name, after applying
@ -376,7 +376,7 @@ module ActiveRecord
end
def _default_attributes # :nodoc:
@default_attributes ||= AttributeSet.new({})
@default_attributes ||= ActiveModel::AttributeSet.new({})
end
# Returns an array of column names as strings.

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true
require "active_record/attribute"
require "active_model/attribute"
module ActiveRecord
class Relation
class QueryAttribute < Attribute # :nodoc:
class QueryAttribute < ActiveModel::Attribute # :nodoc:
def type_cast(value)
value
end

View File

@ -930,7 +930,7 @@ module ActiveRecord
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
if limit_value
limit_attribute = Attribute.with_cast_value(
limit_attribute = ActiveModel::Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type.default_value,
@ -938,7 +938,7 @@ module ActiveRecord
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
end
if offset_value
offset_attribute = Attribute.with_cast_value(
offset_attribute = ActiveModel::Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type.default_value,