Last active
March 4, 2016 17:27
-
-
Save soulcutter/61b4fb897d43095185dd to your computer and use it in GitHub Desktop.
Fluent builder interface
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
module Fluent | |
def fluent_accessor(*attr_names) | |
attr_names.each do |attr_name| | |
define_method attr_name do |*val| | |
return instance_variable_get("@#{attr_name}") if val.empty? | |
raise ArgumentError, "Expected 0..1 arguments, got #{val.size}" if val.size > 1 | |
value = val.first | |
value = yield(value) if block_given? | |
instance_variable_set("@#{attr_name}", value) | |
self | |
end | |
end | |
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
class Profile | |
attr_accessor :first, :last, :middle | |
attr_reader :email | |
def email=(val) | |
@email = val.downcase | |
end | |
end | |
# one-liner without the constructor is hideous | |
Profile.new.tap { |p| p.first = "Brad"; p.middle = "S"; p.email = "FOO@BAR.COM" } | |
# let's try a constructor | |
class Profile | |
# hmm, boilerplatey and sprinkled with nils | |
def initialize(first = nil, last = nil, middle = nil, email: nil) | |
@first = first | |
@last = last | |
@middle = middle | |
self.email = email | |
end | |
end | |
# have to look up the order of args, ugly nils for 'missing' args | |
# keyword arguments would improve this somewhat though | |
Profile.new("Brad", nil, "S", email: "FOO@BAR.COM") | |
# The Fluent version: | |
# - not littered with nils - very readable implementation | |
# - arguments are easily discoverable in Pry/IRB REPL | |
# - Order of args unimportant | |
require 'fluent' | |
class Profile2 | |
extend Fluent | |
fluent_accessor :first, :last, :middle | |
fluent_accessor(:email) { |x| x.downcase } | |
end | |
# One-liner is decent-looking | |
profile = Profile2.new.email("FOO@BAR.COM").first("Brad") | |
profile.email # => "foo@bar.com" | |
# Sometimes makes sense to have a 'terminating' method (especially when | |
# you're not just building a value object) - in this case | |
# we could build up into a frozen Profile instance | |
ImmutableProfile = Struct.new(:first, :middle, :last, :email) do | |
def initialize(*) | |
super | |
freeze | |
end | |
end | |
class Profile2 | |
def to_profile | |
# validation? | |
ImmutableProfile.new(first, middle, last, email) | |
end | |
end | |
Profile2.new.email("FOO@BAR.COM").first("Brad").to_profile | |
# This example is obviously contrived but hopefully illustrates a | |
# technique you may find useful in real-world code. Cheers! | |
# PS - don't be afraid of metaprogramming! | |
# http://tiny.cc/fluent_builder | |
# https://www.youtube.com/watch?v=SpPXewlgmcw&t=15s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment