PREPARE TO REPEL BOARDERS!!!!!!!
with Rails 5.2 Attributes API
What we'll cover
- What is a form object?
- When would you use one?
- ActiveModel::Attributes
- ActiveRecord::Attributes
What is a form object?
# this is an old style form object
# we'll be improving on this.
class Contact
include ActiveModel::Model
attr_accessor :email, :message, :name
validates_presence_of :message...
end
<%= form_for @contact do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :message %>
<%= f.text_field :message %>
<%= f.submit "Send message" %>
<% end %>
When would you use one?
OK but seriously.. what do you use them for?
- Non AR backed model
- Updating multiple objects at once
- When you don't want to bother with accepts_nested_attributes_for
- Also, they're fun to overuse ;)
First.. a quick quiz
Text
What types are the params?
Text
textfield
number_field
{
"textfield"=>"some text",
"numberish"=>"1",
}
STRINGS!!!!!!!!!!
class BarbleForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :foo, :datetime
attribute :bar, :integer
attribute :baz, :string
...
end
You can have defaults...
attribute :start_time, :datetime, default: -> { Time.now }
But wait! there's more....
Domain/Value objects
# app/models/spot.rb
class Spot
def initialize(lat, long)
@lat = lat
@long = long
end
attr_reader :lat, :long
def ==(other)
lat == other.lat && long == other.long
end
end
# app/types/spot_type.rb
class SpotType < ActiveRecord::Type::Value
def cast(value)
#used to take input from params
case value
when String
Spot.new(*value.split(","))
when Spot
value
end
end
end
# config/initializers/types.rb
ActiveModel::Type.register(:spot, SpotType)
class TreasureForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :x, :spot
foo = TreasureForm.new(x: "123.23, 45.123")
foo.x.lat => "123.23"
foo.x.class => Spot
foo = TreasureForm.new(x: Spot.new("123.23", "45.123"))
foo.x.lat => "123.23"
foo.x.class => Spot
ActiveRecord::Attributes
ActiveRecord::Attributes (change schema type)
# db/schema.rb
create_table :store_listings, force: true do |t|
t.decimal :price_in_cents
end
# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
end
store_listing = StoreListing.new(price_in_cents: '10.1')
# before
store_listing.price_in_cents # => BigDecimal(10.1)
class StoreListing < ActiveRecord::Base
attribute :price_in_cents, :integer
end
# after
store_listing.price_in_cents # => 10
rails g model product name:string price_in_pieces_of_eight:integer
Use your value objects to query the database..
# app/models/money.rb
class Money < Struct.new(:amount, :currency)
end
class MoneyType < ActiveRecord::Type::Value
def initialize(gold_converter:)
@gold_converter = gold_converter
end
# value will be the result of +deserialize+ or
# +cast+. Assumed to be an instance of +Money+ in
# this case.
def serialize(value)
value_in_pieces_of_eight = @gold_converter.convert_to_pieces_of_eight(value)
value_in_pieces_of_eight.amount
end
end
Product.create(price_in_pieces_of_eight: Money.new(5000, "USD"))
# INSERT INTO `products` (`price_in_pieces_of_eight`, `created_at`, `updated_at`)
# VALUES (123, '2019-08-10 01:08:15', '2019-08-10 01:08:15')
Product.where(price_in_pieces_of_eight: Money.new(4000, "CAD"))
# SELECT `products`.* FROM `products`
# WHERE `products`.`price_in_pieces_of_eight` = 456 LIMIT 11
Finally...
class BarbleForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :foo, :integer
validates_presence_of :foo
def self.model_name
ActiveModel::Name.new(self, nil, "Barble")
end
def save
return false unless valid?
# do all the things
end
end
Great place to do too many things
Finallier...
class Contact
...
def persisted?
# defaults to false
end
end
class Contact
def id
"sdf"
end
def persisted?
true
end
end
- Internationalization lives on active_model
- Use ActiveModel::Attributes for non AR backed models
- Use ActiveRecord::Attributes for AR backed models
- ActiveModel::Type::Value == ActiveRecord::Type::Value
- postgres `attribute :foo, :integer, array: true`
- Virtus & dry-rb (dry-struct dry-types) ecosystem
Finalliest...
Thanks & Questions?
@psdavey
https://bit.ly/2YZ16CA (Slides)
PREPARE TO REPEL BOARDERS (Rails 5.2 Attributes API)
By Patrick Davey
PREPARE TO REPEL BOARDERS (Rails 5.2 Attributes API)
Rails 5.2 Attributes API
- 869