Skip to content

Instantly share code, notes, and snippets.

@shawn42

shawn42/props.rb Secret

Last active August 29, 2015 14:10
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 shawn42/5a99d9d8914e9f3d679b to your computer and use it in GitHub Desktop.
Save shawn42/5a99d9d8914e9f3d679b to your computer and use it in GitHub Desktop.
Ember like computed properties
require 'active_support/core_ext/hash/indifferent_access'
module Props
def initialize_props
meta = self.class.instance_variable_get('@__props_meta') || Meta.new
self.instance_variable_set('@__props_meta', meta)
props = meta.properties
props.each do |name, prop|
deps = prop.deps
deps.each do |dep|
meta.deps[dep] ||= []
meta.deps[dep] << name
end
end
props
end
def get(name)
meta = self.instance_variable_get('@__props_meta')
return if meta.nil?
prop = meta.properties[name]
return if prop.nil?
if prop.computed?
if meta.cache.has_key? name
meta.cache[name]
else
meta.cache[name] = prop.compute self
end
else
meta.values[name]
end
end
def set(name, value)
meta = self.instance_variable_get('@__props_meta')
return if meta.nil?
prop = meta.properties[name]
return if prop.nil?
if prop.computed?
raise "cannot set computed property"
else
if meta.values[name] != value
meta.deps[name].each do |prop_name|
meta.cache.delete prop_name
end
meta.values[name] = value
end
end
end
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def create(initial_props={})
object = self.new
object.initialize_props
initial_props.each do |name, value|
object.set(name, value)
end
object
end
def prop(prop_name, &blk)
@__props_meta ||= Meta.new
@__props_meta.properties[prop_name] = Prop.new(prop_name, &blk)
@__props_meta.properties[prop_name]
end
end
class Meta
attr_accessor :properties, :values, :cache, :deps
def initialize
@properties = HashWithIndifferentAccess.new
@values = HashWithIndifferentAccess.new
@cache = HashWithIndifferentAccess.new
@deps = HashWithIndifferentAccess.new
end
end
class Prop
attr_reader :name
def initialize(name, &blk)
@name = name
@blk = blk
end
def computed?
!@blk.nil?
end
def compute(target)
@blk.call target
end
def depends_on(*dep_names)
@dep_names = dep_names
end
def deps
@dep_names || []
end
end
end
class Person
include Props
prop :first_name
prop :last_name
prop :full_name do |obj|
"#{obj.get(:first_name)} #{obj.get(:last_name)}"
end.depends_on(:first_name, :last_name)
end
person = Person.create first_name: "Billy", last_name: "Bob"
p person.get(:full_name)
person.set(:first_name, "Darth")
person.set(:last_name, "Vader")
p person.get(:full_name)
p person.get('full_name')
# TODO
# make something.another.foo work? (may need to switch to Signals for that)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment