mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #44306 from volmer/attributes-docs
Documentation for Active Model Attributes [ci-skip]
This commit is contained in:
commit
61468079b6
13 changed files with 286 additions and 34 deletions
|
@ -4,7 +4,30 @@ require "active_model/attribute_set"
|
|||
require "active_model/attribute/user_provided_default"
|
||||
|
||||
module ActiveModel
|
||||
module Attributes # :nodoc:
|
||||
# The Attributes module allows models to define attributes beyond simple Ruby
|
||||
# readers and writers. Similar to Active Record attributes, which are
|
||||
# typically inferred from the database schema, Active Model Attributes are
|
||||
# aware of data types, can have default values, and can handle casting and
|
||||
# serialization.
|
||||
#
|
||||
# To use Attributes, include the module in your model class and define your
|
||||
# attributes using the +attribute+ macro. It accepts a name, a type, a default
|
||||
# value, and any other options supported by the attribute type.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :name, :string
|
||||
# attribute :active, :boolean, default: true
|
||||
# end
|
||||
#
|
||||
# person = Person.new(name: "Volmer")
|
||||
#
|
||||
# person.name # => "Volmer"
|
||||
# person.active # => true
|
||||
module Attributes
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::AttributeMethods
|
||||
|
||||
|
@ -16,6 +39,21 @@ module ActiveModel
|
|||
end
|
||||
|
||||
module ClassMethods
|
||||
# Defines a model attribute. In addition to the attribute name, a cast
|
||||
# type and default value may be specified, as well as any options
|
||||
# supported by the given cast type.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :name, :string
|
||||
# attribute :active, :boolean, default: true
|
||||
# end
|
||||
#
|
||||
# person = Person.new(name: "Volmer")
|
||||
#
|
||||
# person.name # => "Volmer"
|
||||
# person.active # => true
|
||||
def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
|
||||
name = name.to_s
|
||||
|
||||
|
@ -27,7 +65,7 @@ module ActiveModel
|
|||
define_attribute_method(name)
|
||||
end
|
||||
|
||||
# Returns an array of attribute names as strings
|
||||
# Returns an array of attribute names as strings.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
|
@ -36,8 +74,7 @@ module ActiveModel
|
|||
# attribute :age, :integer
|
||||
# end
|
||||
#
|
||||
# Person.attribute_names
|
||||
# # => ["name", "age"]
|
||||
# Person.attribute_names # => ["name", "age"]
|
||||
def attribute_names
|
||||
attribute_types.keys
|
||||
end
|
||||
|
@ -75,7 +112,7 @@ module ActiveModel
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(*)
|
||||
def initialize(*) # :nodoc:
|
||||
@attributes = self.class._default_attributes.deep_dup
|
||||
super
|
||||
end
|
||||
|
@ -85,7 +122,8 @@ module ActiveModel
|
|||
super
|
||||
end
|
||||
|
||||
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
||||
# Returns a hash of all the attributes with their names as keys and the
|
||||
# values of the attributes as values.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
|
@ -94,14 +132,13 @@ module ActiveModel
|
|||
# attribute :age, :integer
|
||||
# end
|
||||
#
|
||||
# person = Person.new(name: 'Francesco', age: 22)
|
||||
# person.attributes
|
||||
# # => {"name"=>"Francesco", "age"=>22}
|
||||
# person = Person.new(name: "Francesco", age: 22)
|
||||
# person.attributes # => { "name" => "Francesco", "age" => 22}
|
||||
def attributes
|
||||
@attributes.to_hash
|
||||
end
|
||||
|
||||
# Returns an array of attribute names as strings
|
||||
# Returns an array of attribute names as strings.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
|
@ -111,13 +148,12 @@ module ActiveModel
|
|||
# end
|
||||
#
|
||||
# person = Person.new
|
||||
# person.attribute_names
|
||||
# # => ["name", "age"]
|
||||
# person.attribute_names # => ["name", "age"]
|
||||
def attribute_names
|
||||
@attributes.keys
|
||||
end
|
||||
|
||||
def freeze
|
||||
def freeze # :nodoc:
|
||||
@attributes = @attributes.clone.freeze unless frozen?
|
||||
super
|
||||
end
|
||||
|
|
|
@ -4,7 +4,21 @@ require "active_model/type/integer"
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class BigInteger < Integer # :nodoc:
|
||||
# Attribute type for integers that can be serialized to an unlimited number
|
||||
# of bytes. This type is registered under the +:big_integer+ key.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :id, :big_integer
|
||||
# end
|
||||
#
|
||||
# person = Person.new(id: "18_000_000_000")
|
||||
# person.id # => 18000000000
|
||||
#
|
||||
# All casting and serialization are performed in the same way as the
|
||||
# standard ActiveModel::Type::Integer type.
|
||||
class BigInteger < Integer
|
||||
private
|
||||
def max_value
|
||||
::Float::INFINITY
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class Binary < Value # :nodoc:
|
||||
# Attribute type for representation of binary data. This type is registered
|
||||
# under the +:binary+ key.
|
||||
#
|
||||
# Non-string values are coerced to strings using their +to_s+ method.
|
||||
class Binary < Value
|
||||
def type
|
||||
:binary
|
||||
end
|
||||
|
|
|
@ -2,17 +2,13 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
# == Active \Model \Type \Boolean
|
||||
# A class that behaves like a boolean type, including rules for coercion of
|
||||
# user input.
|
||||
#
|
||||
# A class that behaves like a boolean type, including rules for coercion of user input.
|
||||
#
|
||||
# === Coercion
|
||||
# Values set from user input will first be coerced into the appropriate ruby type.
|
||||
# Coercion behavior is roughly mapped to Ruby's boolean semantics.
|
||||
#
|
||||
# - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
|
||||
# - Empty strings are coerced to +nil+
|
||||
# - All other values will be coerced to +true+
|
||||
# - <tt>"false"</tt>, <tt>"f"</tt> , <tt>"0"</tt>, +0+ or any other value in
|
||||
# +FALSE_VALUES+ will be coerced to +false+.
|
||||
# - Empty strings are coerced to +nil+.
|
||||
# - All other values will be coerced to +true+.
|
||||
class Boolean < Value
|
||||
FALSE_VALUES = [
|
||||
false, 0,
|
||||
|
|
|
@ -2,7 +2,25 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class Date < Value # :nodoc:
|
||||
# Attribute type for date representation. It is registered under the
|
||||
# +:date+ key.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :birthday, :date
|
||||
# end
|
||||
#
|
||||
# person = Person.new(birthday: "1989-07-13")
|
||||
#
|
||||
# person.birthday.class # => Date
|
||||
# person.birthday.year # => 1989
|
||||
# person.birthday.month # => 7
|
||||
# person.birthday.day # => 13
|
||||
#
|
||||
# String values are parsed using the ISO 8601 date format. Any other values
|
||||
# are cast using their +to_date+ method, if it exists.
|
||||
class Date < Value
|
||||
include Helpers::Timezone
|
||||
include Helpers::AcceptsMultiparameterTime.new
|
||||
|
||||
|
|
|
@ -2,7 +2,41 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class DateTime < Value # :nodoc:
|
||||
# Attribute type to represent dates and times. It is registered under the
|
||||
# +:datetime+ key.
|
||||
#
|
||||
# class Event
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :start, :datetime
|
||||
# end
|
||||
#
|
||||
# event = Event.new(start: "Wed, 04 Sep 2013 03:00:00 EAT")
|
||||
#
|
||||
# event.start.class # => Time
|
||||
# event.start.year # => 2013
|
||||
# event.start.month # => 9
|
||||
# event.start.day # => 4
|
||||
# event.start.hour # => 3
|
||||
# event.start.min # => 0
|
||||
# event.start.sec # => 0
|
||||
# event.start.zone # => "EAT"
|
||||
#
|
||||
# String values are parsed using the ISO 8601 datetime format. Partial
|
||||
# time-only formats are also accepted.
|
||||
#
|
||||
# event.start = "06:07:08+09:00"
|
||||
# event.start.utc # => 1999-12-31 21:07:08 UTC
|
||||
#
|
||||
# The degree of sub-second precision can be customized when declaring an
|
||||
# attribute:
|
||||
#
|
||||
# class Event
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :start, :datetime, precision: 4
|
||||
# end
|
||||
class DateTime < Value
|
||||
include Helpers::Timezone
|
||||
include Helpers::TimeValue
|
||||
include Helpers::AcceptsMultiparameterTime.new(
|
||||
|
|
|
@ -4,7 +4,32 @@ require "bigdecimal/util"
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class Decimal < Value # :nodoc:
|
||||
# Attribute type for decimal, high-precision floating point numeric
|
||||
# representation. It is registered under the +:decimal+ key.
|
||||
#
|
||||
# class BagOfCoffee
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :weight, :decimal
|
||||
# end
|
||||
#
|
||||
# bag = BagOfCoffee.new(weight: "0.0001")
|
||||
# bag.weight # => 0.1e-3
|
||||
#
|
||||
# Numeric instances are converted to BigDecimal instances. Any other objects
|
||||
# are cast using their +to_d+ method, if it exists. If it does not exist,
|
||||
# the object is converted to a string using +to_s+, which is then coerced to
|
||||
# a BigDecimal using +to_d+.
|
||||
#
|
||||
# Decimal precision defaults to 18, and can be customized when declaring an
|
||||
# attribute:
|
||||
#
|
||||
# class BagOfCoffee
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :weight, :decimal, precision: 24
|
||||
# end
|
||||
class Decimal < Value
|
||||
include Helpers::Numeric
|
||||
BIGDECIMAL_PRECISION = 18
|
||||
|
||||
|
|
|
@ -4,7 +4,26 @@ require "active_support/core_ext/object/try"
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class Float < Value # :nodoc:
|
||||
# Attribute type for floating point numeric values. It is registered under
|
||||
# the +:float+ key.
|
||||
#
|
||||
# class BagOfCoffee
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :weight, :float
|
||||
# end
|
||||
#
|
||||
# bag = BagOfCoffee.new(weight: "0.25")
|
||||
# bag.weight # => 0.25
|
||||
#
|
||||
# Values are coerced to their float representation using their +to_f+
|
||||
# methods. However, the following strings which represent floating point
|
||||
# constants are cast accordingly:
|
||||
#
|
||||
# - <tt>"Infinity"</tt> is cast to <tt>Float::INFINITY</tt>.
|
||||
# - <tt>"-Infinity"</tt> is cast to <tt>-Float::INFINITY</tt>.
|
||||
# - <tt>"NaN"</tt> is cast to <tt>Float::NAN</tt>.
|
||||
class Float < Value
|
||||
include Helpers::Numeric
|
||||
|
||||
def type
|
||||
|
|
|
@ -2,7 +2,34 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class ImmutableString < Value # :nodoc:
|
||||
# Attribute type to represent immutable strings. It casts incoming values to
|
||||
# frozen strings.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :name, :immutable_string
|
||||
# end
|
||||
#
|
||||
# person = Person.new
|
||||
# person.name = 1
|
||||
# person.name # => "1"
|
||||
# person.name.frozen? # => true
|
||||
#
|
||||
# Values are coerced to strings using their +to_s+ method. Boolean values
|
||||
# are treated differently, however: +true+ will be cast to <tt>"t"</tt> and
|
||||
# +false+ will be cast to <tt>"f"</tt>. These strings can be customized when
|
||||
# declaring an attribute:
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :active, :immutable_string, true: "aye", false: "nay"
|
||||
# end
|
||||
#
|
||||
# person = Person.new(active: true)
|
||||
# person.active # => "aye"
|
||||
class ImmutableString < Value
|
||||
def initialize(**args)
|
||||
@true = -(args.delete(:true)&.to_s || "t")
|
||||
@false = -(args.delete(:false)&.to_s || "f")
|
||||
|
|
|
@ -2,7 +2,38 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class Integer < Value # :nodoc:
|
||||
# Attribute type for integer representation. This type is registered under
|
||||
# the +:integer+ key.
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :age, :integer
|
||||
# end
|
||||
#
|
||||
# person = Person.new(age: "18")
|
||||
# person.age # => 18
|
||||
#
|
||||
# Values are cast using their +to_i+ method, if it exists. If it does not
|
||||
# exist, or if it raises an error, the value will be cast to +nil+:
|
||||
#
|
||||
# person.age = :not_an_integer
|
||||
# person.age # => nil (because Symbol does not define #to_i)
|
||||
#
|
||||
# Serialization also works under the same principle. Non-numeric strings are
|
||||
# serialized as +nil+, for example.
|
||||
#
|
||||
# Serialization also validates that the integer can be stored using a
|
||||
# limited number of bytes. If it cannot, an ActiveModel::RangeError will be
|
||||
# raised. The default limit is 4 bytes, and can be customized when declaring
|
||||
# an attribute:
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :age, :integer, limit: 6
|
||||
# end
|
||||
class Integer < Value
|
||||
include Helpers::Numeric
|
||||
|
||||
# Column storage size in bytes.
|
||||
|
|
|
@ -4,7 +4,13 @@ require "active_model/type/immutable_string"
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class String < ImmutableString # :nodoc:
|
||||
# Attribute type for strings. It is registered under the +:string+ key.
|
||||
#
|
||||
# This class is a specialization of ActiveModel::Type::ImmutableString. It
|
||||
# performs coercion in the same way, and can be configured in the same way.
|
||||
# However, it accounts for mutable strings, so dirty tracking can properly
|
||||
# check if a string has changed.
|
||||
class String < ImmutableString
|
||||
def changed_in_place?(raw_old_value, new_value)
|
||||
if new_value.is_a?(::String)
|
||||
raw_old_value != new_value
|
||||
|
|
|
@ -2,7 +2,41 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
class Time < Value # :nodoc:
|
||||
# Attribute type for time representation. It is registered under the
|
||||
# +:time+ key.
|
||||
#
|
||||
# class Event
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :start, :time
|
||||
# end
|
||||
#
|
||||
# event = Event.new(start: "2022-02-18T13:15:00-05:00")
|
||||
#
|
||||
# event.start.class # => Time
|
||||
# event.start.year # => 2022
|
||||
# event.start.month # => 2
|
||||
# event.start.day # => 18
|
||||
# event.start.hour # => 13
|
||||
# event.start.min # => 15
|
||||
# event.start.sec # => 0
|
||||
# event.start.zone # => "EST"
|
||||
#
|
||||
# String values are parsed using the ISO 8601 datetime format. Partial
|
||||
# time-only formats are also accepted.
|
||||
#
|
||||
# event.start = "06:07:08+09:00"
|
||||
# event.start.utc # => 1999-12-31 21:07:08 UTC
|
||||
#
|
||||
# The degree of sub-second precision can be customized when declaring an
|
||||
# attribute:
|
||||
#
|
||||
# class Event
|
||||
# include ActiveModel::Attributes
|
||||
#
|
||||
# attribute :start, :time, precision: 4
|
||||
# end
|
||||
class Time < Value
|
||||
include Helpers::Timezone
|
||||
include Helpers::TimeValue
|
||||
include Helpers::AcceptsMultiparameterTime.new(
|
||||
|
|
|
@ -2,9 +2,15 @@
|
|||
|
||||
module ActiveModel
|
||||
module Type
|
||||
# The base class for all attribute types. This class also serves as the
|
||||
# default type for attributes that do not specify a type.
|
||||
class Value
|
||||
attr_reader :precision, :scale, :limit
|
||||
|
||||
# Initializes a type with three basic configuration settings: precision,
|
||||
# limit, and scale. The Value base class does not define behavior for
|
||||
# these settings. It uses them for equality comparison and hash key
|
||||
# generation only.
|
||||
def initialize(precision: nil, limit: nil, scale: nil)
|
||||
@precision = precision
|
||||
@scale = scale
|
||||
|
@ -19,7 +25,9 @@ module ActiveModel
|
|||
true
|
||||
end
|
||||
|
||||
def type # :nodoc:
|
||||
# Returns the unique type name as a Symbol. Subclasses should override
|
||||
# this method.
|
||||
def type
|
||||
end
|
||||
|
||||
# Converts a value from database input to the appropriate ruby type. The
|
||||
|
|
Loading…
Reference in a new issue