Skip to content

Instantly share code, notes, and snippets.

@joegaudet
Created July 9, 2018 16:19
Show Gist options
  • Select an option

  • Save joegaudet/2bcf185ebd4c3d7bff71ebc6e4896606 to your computer and use it in GitHub Desktop.

Select an option

Save joegaudet/2bcf185ebd4c3d7bff71ebc6e4896606 to your computer and use it in GitHub Desktop.
module Values
module Support
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
# Defines a new value_attribute
#
# @usage value_attribute :value_menu, Values::Menus::Menu, builder: :from_ar_menu
#
# @param [Symbol] value_name
# @param [Class] value_class
# @param [Object] options
# @!macro [attach] value_attribute
# @return [$2] the $1 $0
def value_attribute(value_name, value_class, options = {})
builder = options[:builder]
unless value_class < ::Values::Base
raise ArgumentError.new("You tried to define value accessors for #{value_name} using #{value_class} which does not descend from Values::Base")
end
# Defines a setter method that converts your virtus struct into a hash that
# can be stored in a jsonb column
define_method(:"#{value_name}=") do |value|
if value.nil?
super(value)
else
# If a builder has been provided we can call a method provided on the class
# which will take the provided value and convert it into the value class
if builder
value = value_class.send(builder, value)
end
unless value.is_a?(value_class)
raise ArgumentError.new("You tried to set #{value_name} to an instance of #{value.class.name} when it only accepts #{value_class}")
end
super(value.to_hash)
end
end
# defines a getter for the value which gets the jsonb field
# and wraps it in your model
define_method(value_name) do
value = super()
value.present? ? value_class.new(value) : nil
end
end
end
end
end
require "test_helper"
module Values
class SupportTest < ActiveSupport::TestCase
class AValueObject < Values::Base
values do
attribute :foo, String
attribute :bar, String
end
end
# The value_attribute macro works by calling
# super on active record accessor models, so we need
# to provide those.
class PoroBase
attr_accessor :test
def foo=(hash)
@test = hash
end
def foo
@test
end
end
class Poro < PoroBase
include Values::Support
value_attribute :foo, AValueObject
end
def test_assignment_enforces_type
poro = Poro.new
err = -> {
poro.foo = {}
}.must_raise ArgumentError
err.message.must_match /You tried to set foo to/
end
def test_assignment_extracts_a_hash
poro = Poro.new
foo = Faker::Lorem.word
bar = Faker::Lorem.word
poro.foo = AValueObject.new(foo: foo, bar: bar)
assert_equal poro.test, {foo: foo, bar: bar}
end
def test_access_restores_from_a_hash
poro = Poro.new
foo = Faker::Lorem.word
bar = Faker::Lorem.word
poro.test = {foo: foo, bar: bar}
assert_equal poro.foo.foo, foo
assert_equal poro.foo.bar, bar
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment