Skip to content

Instantly share code, notes, and snippets.

@soulcutter
Last active March 4, 2016 17:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soulcutter/61b4fb897d43095185dd to your computer and use it in GitHub Desktop.
Save soulcutter/61b4fb897d43095185dd to your computer and use it in GitHub Desktop.
Fluent builder interface
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
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