Last active
April 18, 2016 21:50
-
-
Save phillmv/945efee1f0de8c00a57ac558b657a8e6 to your computer and use it in GitHub Desktop.
Experiment in meta ruby
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
require 'test_helper' | |
class CanaryBaseTest < ActiveSupport::TestCase | |
describe "CanaryBase#initialize" do | |
it "should handle simple hashes correctly" do | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :param2 | |
end | |
hsh_sym = {:param1 => "test1", :param2 => "test2"} | |
obj = klass.new(hsh_sym) | |
assert_equal hsh_sym, obj.attributes | |
assert obj.param1 | |
assert obj.param2 | |
# we also take strings as keys | |
hsh_str = {"param1" => "test1", "param2" => "test2"} | |
obj_str = klass.new(hsh_str) | |
# strings get parsed but spat out as sym | |
assert_equal hsh_sym, obj_str.attributes | |
assert obj_str.param1 | |
assert obj_str.param2 | |
end | |
it "should only handle whitelisted keys" do | |
klass = Class.new(CanaryBase) do | |
attr_params :param1 | |
end | |
hsh = {:param1 => "test1", :param2 => "nope"} | |
obj = klass.new(hsh) | |
assert_not_equal obj.attributes, hsh | |
assert obj.param1 | |
assert_raises(NoMethodError) do | |
obj.param2 | |
end | |
end | |
it "should handle hash keys with dashes" do | |
klass = Class.new(CanaryBase) do | |
attr_params :heartbeat_at, :an_arbitrary_name | |
end | |
hsh = { "heartbeat-at" => "test1", "an-arbitrary-name" => "test2" } | |
obj = klass.new(hsh) | |
assert obj.heartbeat_at | |
assert obj.an_arbitrary_name | |
assert_equal "test1", obj.attributes[:heartbeat_at] | |
end | |
it "should handle enforce_collection" do | |
klass = Class.new(CanaryBase) do | |
attr_params :heartbeat_at, :many_things | |
attr_enforce_collection :many_things | |
end | |
hsh = { "heartbeat-at" => "test1", "many-things" => "foo" } | |
obj = klass.new(hsh) | |
assert obj.heartbeat_at | |
assert_equal 1, obj.many_things.size | |
assert_equal "foo", obj.many_things[0] | |
hsh_missing = { } | |
obj_missing = klass.new(hsh_missing) | |
assert_equal false, obj_missing.many_things.nil? | |
assert_equal 0, obj_missing.many_things.size | |
end | |
end | |
describe "CanaryBase associations" do | |
describe "has_many" do | |
it "has many associations always return arrays" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :many_objects | |
has_many test_association_class | |
end | |
hsh = { "param1" => "test1", | |
"many_objects" => { "sub_param1" => "test2" } } | |
obj = klass.new(hsh) | |
assert obj.param1 | |
assert obj.many_objects.present? | |
assert_equal 1, obj.many_objects.size | |
assert_equal test_association_class, obj.many_objects.first.class | |
assert_equal "test2", obj.many_objects.first.sub_param1 | |
end | |
it "should accept alternate keys" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :foo | |
has_many test_association_class, "foo" | |
end | |
hsh = { "param1" => "test1", | |
"foo" => { "sub_param1" => "test2" } } | |
obj = klass.new(hsh) | |
assert obj.foo.present? | |
assert_equal 1, obj.foo.size | |
assert_equal test_association_class, obj.foo.first.class | |
assert_equal "test2", obj.foo.first.sub_param1 | |
end | |
it "has many associations should handle actual collections" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :many_objects | |
has_many test_association_class | |
end | |
hsh = { "param1" => "test1", | |
"many_objects" => [ | |
{ "sub_param1" => "test2" }, | |
{ "sub_param1" => "test3" } | |
]} | |
obj = klass.new(hsh) | |
assert obj.param1 | |
assert_equal obj.many_objects.size, 2 | |
assert_equal test_association_class, obj.many_objects[0].class | |
assert_equal "test2", obj.many_objects[0].sub_param1 | |
assert_equal "test3", obj.many_objects[1].sub_param1 | |
end | |
it "when an association collection is empty, it should be []" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :many_objects | |
has_many test_association_class | |
end | |
hsh_arr = { "param1" => "test1", | |
"many_objects" => [] | |
} | |
obj_arr = klass.new(hsh_arr) | |
assert obj_arr.param1 | |
assert_equal true, obj_arr.many_objects.empty? | |
assert_not_equal nil, obj_arr.many_objects | |
assert_equal 0, obj_arr.many_objects.size | |
hsh_nil = { "param1" => "test1", | |
"many_objects" => nil | |
} | |
obj_nil = klass.new(hsh_nil) | |
assert obj_nil.param1 | |
assert_equal true, obj_nil.many_objects.empty? | |
assert_not_equal nil, obj_nil.many_objects | |
assert_equal 0, obj_nil.many_objects.size | |
hsh_missing = { "param1" => "test1",} | |
obj_missing = klass.new(hsh_missing) | |
assert obj_missing.param1 | |
assert_equal true, obj_missing.many_objects.empty? | |
assert_not_equal nil, obj_missing.many_objects | |
assert_equal 0, obj_missing.many_objects.size | |
end | |
end | |
describe "has_one" do | |
it "should parse things properly" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :many_object | |
has_one test_association_class | |
end | |
hsh = { "param1" => "test1", | |
"many_object" => { "sub_param1" => "test2" } } | |
obj = klass.new(hsh) | |
assert obj.param1 | |
assert obj.many_object.present? | |
assert_equal test_association_class, obj.many_object.class | |
assert_equal "test2", obj.many_object.sub_param1 | |
end | |
it "should accept alternate keys" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :foo | |
has_one test_association_class, "foo" | |
end | |
hsh = { "param1" => "test1", | |
"foo" => { "sub_param1" => "test2" } } | |
obj = klass.new(hsh) | |
assert obj.param1 | |
assert obj.foo.present? | |
assert_equal test_association_class, obj.foo.class | |
assert_equal "test2", obj.foo.sub_param1 | |
end | |
it "should give a sensible error when passed a collection" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :many_object | |
has_one test_association_class | |
end | |
hsh = { "param1" => "test1", | |
"many_object" => [{ "sub_param1" => "test2" }] } | |
assert_raises ArgumentError do | |
obj = klass.new(hsh) | |
end | |
end | |
it "should be nil when missing data" do | |
test_association_class = Class.new(CanaryBase) do | |
attr_params :sub_param1 | |
def self.name | |
"many_object" | |
end | |
end | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :many_object | |
has_one test_association_class | |
end | |
hsh = { "param1" => "test1", | |
"many_object" => nil } | |
obj = klass.new(hsh) | |
assert_equal nil, obj.many_object | |
end | |
end | |
end | |
describe "CanaryBase.parse_*" do | |
it "should not take collections" do | |
klass = Class.new(CanaryBase) do | |
attr_params :param1, :param2 | |
end | |
hsh = {:param1 => "test1", :param2 => "test2"} | |
obj = klass.parse(hsh) | |
assert_equal "test1", obj.param1 | |
assert_raises ArgumentError do | |
objects = klass.parse([hsh]) | |
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
module ParamAttributes | |
extend ActiveSupport::Concern | |
included do | |
define_singleton_method(:attr_params) do |*params| | |
@attr_params ||= [] | |
@attr_params.concat params | |
attr_accessor(*params) | |
end | |
define_singleton_method(:attr_enforce_collection) do |*params| | |
@attr_enforce_collection ||= [] | |
@attr_enforce_collection.concat params | |
end | |
end | |
class_methods do | |
# has many ALWAYS assumes a collection | |
def has_many(klass, key = nil) | |
@has_many ||= {} | |
if key.nil? | |
key = klass.model_name.route_key | |
end | |
# ensure we got strs and not syms | |
key = key.to_s | |
@has_many = @has_many.merge({key => klass}) | |
end | |
def has_one(klass, key = nil) | |
@has_one ||= {} | |
if key.nil? | |
key = klass.model_name.singular_route_key | |
end | |
# ensure we got strs and not syms | |
key = key.to_s | |
@has_one = @has_one.merge({key => klass}) | |
end | |
# these parse methods are probably redundant | |
# and should be folded into initialize | |
# | |
# parse_* sets up the canary client, | |
# wraps things if we're enforcing a collection | |
# and then passes off to initialize | |
def parse(attr, canary = nil) | |
if canary | |
self.client = canary | |
end | |
if attr.is_a? Array | |
# this is an error. | |
Rails.logger.error("CanaryBase.parse was passed Array") | |
raise ArgumentError.new("CanaryBase.parse was passed an Array") | |
elsif attr.nil? | |
return nil | |
else | |
self.new(attr) | |
end | |
end | |
# enforce that has_many always is | |
# an array. | |
def parse_many(attr, canary = nil) | |
if canary | |
self.client = canary | |
end | |
all_attrs = wrap_array(attr) | |
all_attrs.map { |params| self.new(params) } | |
end | |
def client=(canary) | |
@canary = canary | |
end | |
def client | |
@canary || Canary.new(nil) | |
end | |
def _attr_params | |
@attr_params || [] | |
end | |
def wrap_array(attr) | |
if attr.nil? | |
return [] | |
elsif !attr.is_a? Array | |
return [attr] | |
else | |
return attr | |
end | |
end | |
end | |
# TODO remember to add parent association | |
# aka belongs_to maybe? | |
def initialize(input_params = {}) | |
attrs = self.attr_params.map(&:to_s) | |
# keep track of our associations | |
coll_association = self.has_many_associations | |
sing_association = self.has_one_associations | |
must_be_coll = attr_enforce_collection_params.map(&:to_s) | |
params = sanitize_param_keys(input_params) | |
attrs.each do |setter| | |
value = params[setter] | |
if klass = coll_association[setter] | |
self.public_send("#{setter}=", klass.parse_many(value)) | |
elsif klass = sing_association[setter] | |
self.public_send("#{setter}=", klass.parse(value)) | |
else | |
if must_be_coll.include?(setter) | |
self.public_send("#{setter}=", wrap_array(value)) | |
else | |
self.public_send("#{setter}=", value) | |
end | |
end | |
end | |
end | |
def attributes | |
Hash[attr_params.map { |k| [k, send(k)] }] | |
end | |
def has_many_associations | |
self.class.instance_variable_get('@has_many') || {} | |
end | |
def has_one_associations | |
self.class.instance_variable_get('@has_one') || {} | |
end | |
def attr_enforce_collection_params | |
self.class.instance_variable_get('@attr_enforce_collection') || [] | |
end | |
def attr_params | |
self.class.instance_variable_get('@attr_params') || [] | |
end | |
protected | |
def sanitize_param_keys(params = {}) | |
new_params = {} | |
params.each_pair do |k, v| | |
key = k.to_s.tr("-", "_") | |
new_params[key] = v | |
end | |
new_params | |
end | |
def canary | |
self.class.client | |
end | |
def wrap_array(args) | |
self.class.wrap_array(args) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment