Last active
November 7, 2022 14:59
-
-
Save vlado/fe847c59954fc4c637b5fb57237cd4ef to your computer and use it in GitHub Desktop.
Value Objects in Ruby (and Rails)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# create_table :products do |t| | |
# t.integer :price_in_cents | |
# ... | |
# end | |
class Product < ApplicationRecord | |
VAT = 25 # % | |
def price_in_eur | |
price_in_cents / 100.0 | |
end | |
def price_with_vat_in_eur | |
(price_in_cents + (price_in_cents * VAT / 100.0).round(2)) / 100.0 | |
end | |
end | |
module ProductsHelper | |
HRK_EXCHANGE_RATE = 7.53450 | |
def display_product_price_in_hrk(product) | |
hrk = (product.price_in_eur * HRK_EXCHANGE_RATE).round(2) | |
number_to_currency(price_in_hrk, currency: "HRK") | |
end | |
def display_product_price_with_vat_in_hrk(product) | |
hrk = (product.price_with_vat_in_eur * HRK_EXCHANGE_RATE).round(2) | |
number_to_currency(hrk, currency: "HRK") | |
end | |
def total_price(products) | |
products.sum { |product| product.price_in_eur } | |
end | |
def total_price_with_vat(products) | |
products.sum { |product| product.price_with_vat_in_eur } | |
end | |
end | |
class Price | |
include Comparable | |
HRK_EXCHANGE_RATE = 7.53450 | |
VAT = 25 # % | |
attr_reader :cents | |
def initialize(cents) | |
@cents = cents | |
end | |
def eur | |
cents / 100.0 | |
end | |
def hrk | |
(eur * HRK_EXCHANGE_RATE).round(2) | |
end | |
def with_vat | |
cents_with_vat = cents + (cents * VAT / 100.0).round(2) | |
Price.new(cents_with_vat) | |
end | |
def +(other_price) | |
Price.new(cents + other_price.cents) | |
end | |
def <=>(other_price) | |
cents <=> other_price.cents | |
end | |
end | |
# create_table :products do |t| | |
# t.integer :price_in_cents | |
# ... | |
# end | |
class Product < ApplicationRecord | |
attr_accessor :price_in_cents | |
def price | |
Price.new(price_in_cents) | |
end | |
def price=(new_price) | |
self.price_in_cents = new_price.cents | |
end | |
end | |
price = Price.new(10_000) | |
puts price.eur # => 100.00 | |
puts price.hrk # => 753.45 | |
hundred_euros = Price.new(10_000) | |
p1 = Product.new(price: hundred_euros) | |
puts p1.price.eur # => 100.00 | |
puts p1.price.hrk # => 753.45 | |
puts p1.price.with_vat.eur # => 125.00 | |
puts p1.price.with_vat.hrk # => 941.81 | |
thousand_euros = Price.new(100_000) | |
p2 = Product.new(price: thousand_euros) | |
puts p2.price > p1.price # => true | |
total_price = p1.price + p2.price | |
p total_price # => #<Price:0x00007fcdcf85a940 @cents=110000> | |
puts total_price.with_vat.eur # => 1375.00 | |
hundred_euros = Price.new(10_000) | |
seven_hundred_euros = Price.new(70_000) | |
thousand_euros = Price.new(100_000) | |
hundred_euros == thousand_euros # => false | |
hundred_euros == Price.new(10_000) # => true | |
hundred_euros > seven_hundred_euros # => false | |
seven_hundred_euros < thousand_euros # => true | |
seven_hundred_euros.between?(hundred_euros, thousand_euros) # => true | |
[seven_hundred_euros, thousand_euros, hundred_euros].sort | |
# => [#<Price:0x00007fcdcf85a940 @cents=10000>, #<Price:0x00007fcdcf85a941 @cents=70000>, #<Price:0x00007fcdcf85a942 @cents=100000>] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Address | |
attr_reader :city, :zip, :street, :house_no | |
def initialize(city, zip, street, house_no) | |
@city, @zip, @street, @house_no = city, zip, street, house_no | |
end | |
def ==(other_address) | |
city == other_address.city && zip == other_address.zip && street == other_address.street && house == other_address.house_no | |
end | |
def same_city?(other_address) | |
city == other_address.city | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Price = Data.define(:amount, :currency) do | |
def with_vat | |
Price.new(amount * 1.25, currency) | |
end | |
end | |
hundred_euros = Price.new(100, "EUR") | |
thousand_euros = Price.new(1000, "EUR") | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
date1 = Date.new(2022, 9, 13) | |
date2 = Date.new(2022, 9, 13) | |
puts date1.object_id # => 60 | |
puts date2.object_id # => 80 | |
puts date1.equal?(date2) # => false | |
puts date1.eql?(date2) # => true | |
puts date1 == date2 # => true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Money | |
# Remove setters (replace attr_accessor with attr_reader) | |
attr_reader :amount, :currency | |
def initialize(amount, currency) | |
@amount = amount | |
@currency = currency | |
end | |
# If you really need a setter initialize a new value object | |
# instead of modifying the current one | |
def with_amount(new_amount) | |
Money.new(new_amount, currency) | |
end | |
end | |
eur = Money.new(10, "EUR") | |
p eur # => #<Money:0x00007fd89f84ecc8 @amount=10, @currency="EUR"> | |
other_eur = eur.with_amount(20) | |
p other_eur #<Money:0x00007fcb55872558 @amount=20, @currency="EUR"> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Money | |
include Comparable | |
attr_reader :cents | |
def initialize(cents) | |
@cents = cents | |
end | |
def <=>(other_money) | |
cents <=> other_money.cents | |
end | |
end | |
two_euros = Money.new(200) | |
four_euros = Money.new(400) | |
puts four_euros > two_euros # => true | |
p [four_euros, two_euros].sort # => [#<Money:0x00007f837905d9e0 @cents=200>, #<Money:0x00007f837905d968 @cents=400>] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Money | |
attr_accessor :amount, :currency | |
def initialize(amount, currency) | |
@amount = amount | |
@currency = currency | |
end | |
def to_s | |
"#{amount} #{currency}" | |
end | |
end | |
eur = Money.new(10, "EUR") | |
p eur # => #<Money:0x00007fd89f84ecc8 @amount=10, @currency="EUR"> | |
eur.amount = 20 | |
p eur # => #<Money:0x00007fd89f84ecc8 @amount=20, @currency="EUR"> | |
puts eur # => "10 EUR" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
puts :ruby.object_id # => 707228 | |
puts :ruby.object_id # => 707228 | |
puts :ruby.equal?(:ruby) # => true | |
puts nil.object_id # => 8 | |
puts nil.object_id # => 8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Temperature | |
OPTIMAL = 22 | |
def initialize(degrees) | |
@degrees = degrees | |
end | |
def cold? | |
@degrees < OPTIMAL | |
end | |
def hot? | |
@degrees > OPTIMAL | |
end | |
def optimal? | |
@degrees == OPTIMAL | |
end | |
end | |
t1 = Temperature.new(20) | |
t2 = Temperature.new(22) | |
puts t1.optimal? # => false | |
puts t1.hot? # => false | |
puts t1.cold? # => true | |
puts t2.optimal? # => true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# create_table :events do |t| | |
# t.datetime :start_time | |
# t.datetime :end_time | |
# ... | |
# end | |
class Event < ApplicationRecord | |
attr_accessor :start_time, :end_time | |
def formatted_time | |
from_str = start_time.strftime("%d.%m.%y %H:%M") | |
till_str = end_time.strftime("%d.%m.%y %H:%M") | |
"#{from_str} - #{till_str}" | |
end | |
def duration | |
end_time - start_time | |
end | |
def multi_day? | |
start_time.to_date != end_time.to_date | |
end | |
def include_date?(date) | |
date >= start_time.to_date && date <= end_time.to_date | |
end | |
def overlap_time_period?(other_start_time, other_end_time) | |
start_time <= other_start_time && other_end_time <= end_time | |
end | |
end | |
# VS | |
class TimePeriod | |
attr_reader :start_time, :end_time | |
def initialize(start_time, end_time) | |
@start_time = start_time | |
@end_time = end_time | |
end | |
def to_s | |
from_str = start_time.strftime("%d.%m.%y %H:%M") | |
till_str = end_time.strftime("%d.%m.%y %H:%M") | |
"#{from_str} - #{till_str}" | |
end | |
def duration | |
end_time - start_time | |
end | |
def multi_day? | |
start_time.to_date != end_time.to_date | |
end | |
def include_date?(date) | |
date >= start_time.to_date && date <= end_time.to_date | |
end | |
def overlap_time_period?(other_time_period) | |
start_time <= other_time_period.start_time && other_time_period.end_time <= end_time | |
end | |
end | |
class Event | |
def time_period | |
TimePeriod.new(start_time, end_time) | |
end | |
end | |
event = Event.new(start_time: Time.current, end_time: 4.hours.from_now) | |
event.time_period.to_s | |
# => "06.11.2022 11:09 - 06.11.2022 15:09" | |
event.time_period.duration | |
# => 14399.99786 | |
event.time_period.multi_day? | |
# => false | |
event.time_period.include_date?(Time.zone.today) | |
# => true | |
other_event = Event.new(start_time: Time.current, end_time: 2.hours.from_now) | |
event.time_period.overlap_time_period?(other_event.time_period) | |
# => true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment