1
0
Fork 0
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:
Jonathan Hefner 2022-02-09 14:33:09 -06:00 committed by GitHub
commit 61468079b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 286 additions and 34 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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.

View file

@ -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

View file

@ -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(

View file

@ -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