diff --git a/README.md b/README.md index 3acf86d..22b1520 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ was defined on ```view_context``` (by Rails, which copies them from the Controll so became automatically available inside DSL. Note that all instance variables must be defined *before* the DSL instance is created. -### Method Name Collisions in the DSL +### Method Name Collisions and Override in the DSL Because DSL uses only the last word of the class name (eg, ```user``` for a class named ```MyModule::UserCompositor```), there is a possibility of name collisions. In order to prevent that, Compositor will detect if a DSL method is already @@ -182,6 +182,46 @@ defined and throw exception if another class tries to redefine it. If you prefer to have your own ```Compositor``` class hierarchy, or just compositors that should not be added to the DSL, you can name the classes starting with ```Abstract```, such as ```MyModule::AbstractCompositor```. +The option does exist to Override the DSL method name for named leafs. This will allow you to for example have two or more +User classes defined for composites in a hierarchy, each with a different logical name. For example: + +```ruby +# Will get the 'user' dsl method name +class User < Compositor::Leaf + # methods omitted +end + +# Will get v0_user dsl method name +module Api + module V0 + class User < Compositor::NamedLeaf("v0_user") + # methods omitted + end + end +end + +# Will get v1_user method name +module Api + module V1 + class User < Compositor::NamedLeaf("v1_user") + # methods omitted + end + end +end +``` + +Please note, if you try to use the same named leaf logical name an error will occur. For example: + +```ruby + class Foo < Compositor::NamedLeaf("baz") + # methods omitted + end + + class Bar < Compositor::NamedLeaf("baz") + # methods omitted + end +``` + ### Performance Note of caution: despite the fact that typical DSL generation can take mere 50-100 microseconds, defining complex responses diff --git a/lib/compositor/base.rb b/lib/compositor/base.rb index c30e573..c2b004b 100644 --- a/lib/compositor/base.rb +++ b/lib/compositor/base.rb @@ -1,5 +1,6 @@ module Compositor - class MethodAlreadyDefinedError < RuntimeError; end + class MethodAlreadyDefinedError < RuntimeError; + end class Base attr_reader :attrs @@ -37,21 +38,24 @@ def root_class_name self.class.root_class_name(self.class) end + def self.root_class_name(klazz) + return klazz.dsl_override if klazz.respond_to?(:dsl_override) + return nil if klazz.name.nil? # Anonymous classes will have nil name, we do not want to have them included klazz.name.gsub(/(.*::)|(Compositor$)/, '').underscore end def self.inherited(subclass) method_name = root_class_name(subclass) - unless method_name.eql?("base") || method_name.start_with?("abstract") + unless (method_name.nil? || method_name.eql?("base") || method_name.start_with?("abstract")) # check if it's already defined if Compositor::DSL.instance_methods.include?(method_name.to_sym) raise MethodAlreadyDefinedError.new("Method #{method_name} is already defined on the DSL class.") end Compositor::DSL.send(:define_method, method_name) do |*args, &block| subclass. - new(@context, *args). - dsl(self, &block) + new(@context, *args). + dsl(self, &block) end end end diff --git a/lib/compositor/leaf.rb b/lib/compositor/leaf.rb index 54a111b..71558d9 100644 --- a/lib/compositor/leaf.rb +++ b/lib/compositor/leaf.rb @@ -24,4 +24,31 @@ def dsl(dsl) end end end + + # Create a named leaf, To override the dsl name at the class level + # Note we use an interstitial anonymous class since the DSL builder relies + # on self.inherited. Could get more in terms of flexibility if this used + # module inheritance instead. But this works fine with snytax borrows from + # Camping and Sequal. + # + # Example: + # + #module Motley + # class Crew < Compositor::NamedLeaf("motley_crew") + # end + #end + # + #module TwoLive + # class Crew < Compositor::NamedLeaf("two_live_crew") + # end + #end + + + def self.NamedLeaf name + Class.new(Compositor::Leaf) do + eigenclass = class << self; self; end; + eigenclass.send(:define_method, :dsl_override) { name } + end + end + end diff --git a/spec/compositor/base_spec.rb b/spec/compositor/base_spec.rb index 0a09aff..0e34d6d 100644 --- a/spec/compositor/base_spec.rb +++ b/spec/compositor/base_spec.rb @@ -40,5 +40,48 @@ class AbstractUserCompositor < Compositor::Leaf Compositor::DSL.instance_methods.should_not include(:abstract_user) end + describe "#override_root_class_name" do + + it "override does work" do + lambda { + + class Crew < Compositor::NamedLeaf "crew" + end + + module Motley + class Crew < Compositor::NamedLeaf "motley_crew" + end + end + + module TwoLive + class Crew < Compositor::NamedLeaf "two_live_crew" + end + end + + module WorldClassWrecking + class Crew < Compositor::NamedLeaf "world_class_wrecking_crew" + end + end + }.call + + [:crew, :motley_crew, :world_class_wrecking_crew].each do |dsl_name| + Compositor::DSL.instance_methods.should include(dsl_name) + end + end + + it "Fails with same override" do + expect do + lambda { + class Gang < Compositor::NamedLeaf "gang" + end + + module KoolAndThe + class Gang < Compositor::NamedLeaf "gang" + end + end + }.call + end.to raise_error + end + end end end diff --git a/spec/compositor/hash_spec.rb b/spec/compositor/hash_spec.rb index 89d507c..90d9a5b 100644 --- a/spec/compositor/hash_spec.rb +++ b/spec/compositor/hash_spec.rb @@ -5,11 +5,11 @@ it 'returns the generated map' do expected = { - tests: { - num1: {number: 1}, - num2: {number: 2}, - num3: {number: 3} - } + tests: { + num1: {number: 1}, + num2: {number: 2}, + num3: {number: 3} + } } dsl = Compositor::DSL.create(context) @@ -24,15 +24,15 @@ it 'returns the generated deeply nested map without explicit receiver' do expected = { - tests: { - num1: {number: 1}, - num2: {number: 2}, - num3: {number: 3}, - stuff: [ - {number: 10}, - {number: 11} - ] - } + tests: { + num1: {number: 1}, + num2: {number: 2}, + num3: {number: 3}, + stuff: [ + {number: 10}, + {number: 11} + ] + } } dsl = Compositor::DSL.create(context) @@ -58,4 +58,39 @@ {}.should == dsl.to_hash end end + + describe "Same top level class names" do + it "supports both classes" do + expected = { + :tests => + { + :employee => { + :name => "squiggy", + :salary => 1000, + :status => "baller" + }, + :peasent => { + :name => "bob", + :age => 12 + }, + :internal_ballers => [ + {:name => "squiggy", :salary => 1000000, :status => "baller"}, + {:name => "squiggy", :salary => 2000000, :status => "baller"} + ] + } + } + + dsl_hash = Compositor::DSL.create(context) do + map root: :tests do + internal_person 1000, root: :employee + api_person root: :peasent + list :root => :internal_ballers do + internal_person 1000000 + internal_person 2000000 + end + end + end.to_hash + expected.should == dsl_hash + end + end end diff --git a/spec/support/sample_dsl.rb b/spec/support/sample_dsl.rb index ce68f2a..917e05e 100644 --- a/spec/support/sample_dsl.rb +++ b/spec/support/sample_dsl.rb @@ -33,3 +33,60 @@ def to_hash } end end + +module PublicApi + class Person < Compositor::NamedLeaf("api_person") + def to_hash + with_root_element do + { + name: "bob", + age: 12 + } + end + end + end +end + +module PrivateApi + class Person < Compositor::NamedLeaf("internal_person") + attr_accessor :salary + + def initialize(view_context, salary, attrs = {}) + super(view_context, {salary: salary}.merge!(attrs)) + end + + def to_hash + with_root_element do + { + name: "squiggy", + salary: salary, + status: "baller" + } + end + end + end +end + + +module Api + module V0 + class User < Compositor::NamedLeaf("v0_user") + def to_hash + # hash of stuff for v0 rep of user + end + end + end +end + +module Api + module V1 + class User < Compositor::NamedLeaf("v1_user") + def to_hash + # hash of stuff v1 rep of user + end + end + end +end + + +