uber-0.1.0/0000755000175000017500000000000013137323622011473 5ustar pravipraviuber-0.1.0/uber.gemspec0000644000175000017500000000145213137323622013777 0ustar pravipravi# -*- encoding: utf-8 -*- require File.expand_path('../lib/uber/version', __FILE__) Gem::Specification.new do |gem| gem.authors = ["Nick Sutterer"] gem.email = ["apotonick@gmail.com"] gem.description = %q{A gem-authoring framework.} gem.summary = %q{Gem-authoring tools like generic builders, dynamic options and more.} gem.homepage = "https://github.com/apotonick/uber" gem.license = "MIT" gem.files = `git ls-files`.split($\) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.name = "uber" gem.require_paths = ["lib"] gem.version = Uber::VERSION gem.add_development_dependency "rake" gem.add_development_dependency "minitest" end uber-0.1.0/README.md0000644000175000017500000002274513137323622012764 0ustar pravipravi# Uber _Gem-authoring tools like class method inheritance in modules, dynamic options and more._ ## Installation [![Gem Version](https://badge.fury.io/rb/uber.svg)](http://badge.fury.io/rb/uber) Add this line to your application's Gemfile: ```ruby gem 'uber' ``` Uber runs with Ruby >= 1.9.3. # Inheritable Class Attributes If you want inherited class attributes, this is for you. This is a mandatory mechanism for creating DSLs. ```ruby require 'uber/inheritable_attr' class Song extend Uber::InheritableAttr inheritable_attr :properties self.properties = [:title, :track] # initialize it before using it. end ``` Note that you have to initialize your class attribute with whatever you want - usually a hash or an array. ```ruby Song.properties #=> [:title, :track] ``` A subclass of `Song` will have a `clone`d `properties` class attribute. ```ruby class Hit < Song end Hit.properties #=> [:title, :track] ``` The cool thing about the inheritance is: you can work on the inherited attribute without any restrictions. It is a _copy_ of the original. ```ruby Hit.properties << :number Hit.properties #=> [:title, :track, :number] Song.properties #=> [:title, :track] ``` It's similar to ActiveSupport's `class_attribute` but with a simpler implementation. It is less dangerous. There are no restrictions for modifying the attribute. [compared to `class_attribute`](http://apidock.com/rails/v4.0.2/Class/class_attribute). ## Uncloneable Values `::inheritable_attr` will `clone` values to copy them to subclasses. Uber won't attempt to clone `Symbol`, `nil`, `true` and `false` per default. If you assign any other unclonable value you need to tell Uber that. ```ruby class Song extend Uber::InheritableAttr inheritable_attr :properties, clone: false ``` This won't `clone` but simply pass the value on to the subclass. # Dynamic Options Implements the pattern of defining configuration options and dynamically evaluating them at run-time. Usually DSL methods accept a number of options that can either be static values, symbolized instance method names, or blocks (lambdas/Procs). Here's an example from Cells. ```ruby cache :show, tags: lambda { Tag.last }, expires_in: 5.mins, ttl: :time_to_live ``` Usually, when processing these options, you'd have to check every option for its type, evaluate the `tags:` lambda in a particular context, call the `#time_to_live` instance method, etc. This is abstracted in `Uber::Options` and could be implemented like this. ```ruby require 'uber/options' options = Uber::Options.new(tags: lambda { Tag.last }, expires_in: 5.mins, ttl: :time_to_live) ``` Just initialize `Options` with your actual options hash. While this usually happens on class level at compile-time, evaluating the hash happens at run-time. ```ruby class User < ActiveRecord::Base # this could be any Ruby class. # .. lots of code def time_to_live(*args) "n/a" end end user = User.find(1) options.evaluate(user, *args) #=> {tags: "hot", expires_in: 300, ttl: "n/a"} ``` ## Evaluating Dynamic Options To evaluate the options to a real hash, the following happens: * The `tags:` lambda is executed in `user` context (using `instance_exec`). This allows accessing instance variables or calling instance methods. * Nothing is done with `expires_in`'s value, it is static. * `user.time_to_live?` is called as the symbol `:time_to_live` indicates that this is an instance method. The default behaviour is to treat `Proc`s, lambdas and symbolized `:method` names as dynamic options, everything else is considered static. Optional arguments from the `evaluate` call are passed in either as block or method arguments for dynamic options. This is a pattern well-known from Rails and other frameworks. ## Uber::Callable A third way of providing a dynamic option is using a "callable" object. This saves you the unreadable lambda syntax and gives you more flexibility. ```ruby require 'uber/callable' class Tags include Uber::Callable def call(context, *args) [:comment] end end ``` By including `Uber::Callable`, uber will invoke the `#call` method on the specified object. Note how you simply pass an instance of the callable object into the hash instead of a lambda. ```ruby options = Uber::Options.new(tags: Tags.new) ``` ## Option `Uber::Option` implements the pattern of taking an option, such as a proc, instance method name, or static value, and evaluate it at runtime without knowing the option's implementation. Creating `Option` instances via `::[]` usually happens on class-level in DSL methods. ```ruby with_proc = Uber::Option[ ->(options) { "proc: #{options.inspect}" } ] with_static = Uber::Option[ "Static value" ] with_method = Uber::Option[ :name_of_method ] def name_of_method(options) "method: #{options.inspect}" end ``` Use `#call` to evaluate the options at runtime. ```ruby with_proc.(1, 2) #=> "proc: [1, 2]" with_static.(1, 2) #=> "Static value" # arguments are ignored with_method.(self, 1, 2) #=> "method: [1, 2]" # first arg is context ``` It's also possible to evaluate a callable object. It has to be marked with `Uber::Callable` beforehand. ```ruby class MyCallable include Uber::Callable def call(context, *args) "callable: #{args.inspect}, #{context}" end end with_callable = Uber::Option[ MyCallable.new ] ``` The context is passed as first argument. ```ruby with_callable.(Object, 1, 2) #=> "callable: [1, 2] Object" ``` You can also make blocks being `instance_exec`ed on the context, giving a unique API to all option types. ```ruby with_instance_proc = Uber::Option[ ->(options) { "proc: #{options.inspect} #{self}" }, instance_exec: true ] ``` The first argument now becomes the context, exactly the way it works for the method and callable type. ```ruby with_instance_proc.(Object, 1, 2) #=> "proc [1, 2] Object" ``` # Delegates Using `::delegates` works exactly like the `Forwardable` module in Ruby, with one bonus: It creates the accessors in a module, allowing you to override and call `super` in a user module or class. ```ruby require 'uber/delegates' class SongDecorator def initialize(song) @song = song end attr_reader :song extend Uber::Delegates delegates :song, :title, :id # delegate :title and :id to #song. def title super.downcase # this calls the original delegate #title. end end ``` This creates readers `#title` and `#id` which are delegated to `#song`. ```ruby song = SongDecorator.new(Song.create(id: 1, title: "HELLOWEEN!")) song.id #=> 1 song.title #=> "helloween!" ``` Note how `#title` calls the original title and then downcases the string. # Builder Builders are good for polymorphically creating objects without having to know where that happens. You define a builder with conditions in one class, and that class takes care of creating the actual desired class. ## Declarative Interface Include `Uber::Builder` to leverage the `::builds` method for adding builders, and `::build!` to run those builders in a given context and with arbitrary options. ```ruby require "uber/builder" class User include Uber::Builder builds do |options| Admin if params[:admin] end end class Admin end ``` Note that you can call `builds` as many times as you want per class. Run the builders using `::build!`. ```ruby User.build!(User, {}) #=> User User.build!(User, { admin: true }) #=> Admin ``` The first argument is the context in which the builder blocks will be executed. This is also the default return value if all builders returned a falsey value. All following arguments will be passed straight through to the procs. Your API should communicate `User` as the only public class, since the builder hides details about computing the concrete class. ### Builder: Procs You may also use procs instead of blocks. ```ruby class User include Uber::Builder builds ->(options) do return SignedIn if params[:current_user] return Admin if params[:admin] Anonymous end end ``` Note that this allows `return`s in the block. ## Builder: Direct API In case you don't want the `builds` DSL, you can instantiate a `Builders` object yourself and add builders to it using `#<<`. ```ruby MyBuilders = Uber::Builder::Builders.new MyBuilders << ->(options) do return Admin if options[:admin] end ``` Note that you can call `Builders#<<` multiple times per instance. Invoke the builder using `#call`. ```ruby MyBuilders.call(User, {}) #=> User MyBuilders.call(User, { admin: true }) #=> Admin ``` Again, the first object is the context/default return value, all other arguments are passed to the builder procs. ## Builder: Contexts Every proc is `instance_exec`ed in the context you pass into `build!` (or `call`), allowing you to define generic, shareable builders. ```ruby MyBuilders = Uber::Builder::Builders.new MyBuilders << ->(options) do return self::Admin if options[:admin] # note the self:: ! end class User class Admin end end class Instructor class Admin end end ``` Now, depending on the context class, the builder will return different classes. ```ruby MyBuilders.call(User, {}) #=> User MyBuilders.call(User, { admin: true }) #=> User::Admin MyBuilders.call(Instructor, {}) #=> Instructor MyBuilders.call(Instructor, { admin: true }) #=> Instructor::Admin ``` Don't forget the `self::` when writing generic builders, and write tests. # License Copyright (c) 2014 by Nick Sutterer Uber is released under the [MIT License](http://www.opensource.org/licenses/MIT). uber-0.1.0/lib/0000755000175000017500000000000013137323622012241 5ustar pravipraviuber-0.1.0/lib/uber.rb0000644000175000017500000000010313137323622013515 0ustar pravipravirequire "uber/version" module Uber # Your code goes here... end uber-0.1.0/lib/uber/0000755000175000017500000000000013137323622013176 5ustar pravipraviuber-0.1.0/lib/uber/options.rb0000644000175000017500000000370213137323622015220 0ustar pravipravirequire "uber/callable" require "uber/option" module Uber class Options < Hash def initialize(options) @static = options options.each do |k,v| self[k] = Option[v, instance_exec: true] end end # Evaluates every element and returns a hash. Accepts context and arbitrary arguments. def evaluate(context, *args) {}.tap do |evaluated| each do |k,v| evaluated[k] = v.(context, *args) end end end # Evaluates a single value. def eval(key, *args) self[key].(*args) end private # DEPRECATED! PLEASE USE UBER::OPTION. class Value # TODO: rename to Value. def initialize(value, options={}) @value, @dynamic = value, options[:dynamic] @proc = proc? @callable = callable? @method = method? return if options.has_key?(:dynamic) @dynamic = @proc || @callable || @method end def call(context, *args) return @value unless dynamic? evaluate_for(context, *args) end alias_method :evaluate, :call def dynamic? @dynamic end def proc? @value.kind_of?(Proc) end def callable? @value.is_a?(Uber::Callable) end def method? @value.is_a?(Symbol) end private def evaluate_for(*args) return proc!(*args) if @proc return callable!(*args) if @callable method!(*args) # TODO: change to context.instance_exec and deprecate first argument. end def method!(context, *args) context.send(@value, *args) end def proc!(context, *args) if context.nil? @value.call(*args) else context.instance_exec(*args, &@value) end end # Callable object is executed in its original context. def callable!(context, *args) @value.call(context, *args) end end end end uber-0.1.0/lib/uber/option.rb0000644000175000017500000000075613137323622015043 0ustar pravipravirequire "uber/callable" module Uber class Option def self.[](value, options={}) # TODO: instance_exec: true if value.is_a?(Proc) return ->(context, *args) { context.instance_exec(*args, &value) } if options[:instance_exec] return value end return value if value.is_a?(Uber::Callable) return ->(context, *args){ context.send(value, *args) } if value.is_a?(Symbol) ->(*) { value } end end end uber-0.1.0/lib/uber/callable.rb0000644000175000017500000000035413137323622015264 0ustar pravipravimodule Uber # Include this module into a class or extend an object to mark it as callable. # E.g., in a dynamic option in Options::Value it will be treated like a Proc object # and invoked with +#call+. module Callable end enduber-0.1.0/lib/uber/inheritable_attr.rb0000644000175000017500000000206613137323622017047 0ustar pravipravimodule Uber module InheritableAttr def inheritable_attr(name, options={}) instance_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{name}=(v) @#{name} = v end def #{name} return @#{name} if instance_variable_defined?(:@#{name}) @#{name} = InheritableAttribute.inherit_for(self, :#{name}, #{options}) end RUBY end def self.inherit_for(klass, name, options={}) return unless klass.superclass.respond_to?(name) value = klass.superclass.send(name) # could be nil return value if options[:clone] == false Clone.(value) # this could be dynamic, allowing other inheritance strategies. end class Clone # The second argument allows injecting more types. def self.call(value, uncloneable=uncloneable()) uncloneable.each { |klass| return value if value.kind_of?(klass) } value.clone end def self.uncloneable [Symbol, TrueClass, FalseClass, NilClass] end end end InheritableAttribute = InheritableAttr end uber-0.1.0/lib/uber/version.rb0000644000175000017500000000004413137323622015206 0ustar pravipravimodule Uber VERSION = "0.1.0" end uber-0.1.0/lib/uber/delegates.rb0000644000175000017500000000033613137323622015462 0ustar pravipravirequire 'forwardable' module Uber module Delegates def delegates(model, *names) mod = Module.new do extend Forwardable def_delegators model, *names end include mod end end enduber-0.1.0/lib/uber/builder.rb0000644000175000017500000000141613137323622015153 0ustar pravipravirequire "uber/option" module Uber module Builder def self.included(base) base.extend DSL base.extend Build end class Builders < Array def call(context, *args) each do |block| klass = block.(context, *args) and return klass # Uber::Value#call() end context end def <<(proc) super Uber::Option[proc, instance_exec: true] end end module DSL def builders @builders ||= Builders.new end def builds(proc=nil, &block) builders << (proc || block) end end module Build # Call this from your class to compute the concrete target class. def build!(context, *args) builders.(context, *args) end end end end uber-0.1.0/.travis.yml0000644000175000017500000000016613137323622013607 0ustar pravipravirvm: - 1.9.3 - 2.0.0 - 2.1 - 2.2 - 2.3.1 - jruby-19mode - jruby before_install: - gem install bundler uber-0.1.0/.gitignore0000644000175000017500000000023213137323622013460 0ustar pravipravi*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp uber-0.1.0/LICENSE0000644000175000017500000000205513137323622012502 0ustar pravipraviCopyright (c) 2012 Nick Sutterer MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.uber-0.1.0/Rakefile0000644000175000017500000000041013137323622013133 0ustar pravipravi#!/usr/bin/env rake require "bundler/gem_tasks" require 'rake/testtask' desc 'Test the representable gem.' task :default => :test Rake::TestTask.new(:test) do |test| test.libs << 'test' test.test_files = FileList['test/*_test.rb'] test.verbose = true end uber-0.1.0/Gemfile0000644000175000017500000000015613137323622012770 0ustar pravipravisource 'https://rubygems.org' # Specify your gem's dependencies in uber.gemspec gemspec gem "benchmark-ips" uber-0.1.0/test/0000755000175000017500000000000013137323622012452 5ustar pravipraviuber-0.1.0/test/zeugs.rb0000644000175000017500000000161713137323622014141 0ustar pravipravimodule Feature module ClassMethods def feature end end # in uber, this would look somehow like # module Feature # module ClassMethods ... end # extend Uber::InheritableIncluded # inheritable_included do |includer| # includer.extend ClassMethods # end # end InheritedIncludedCodeBlock = lambda do |includer| includer.extend ClassMethods end module RecursiveIncluded def included(includer) #super # TODO: test me. puts "RecursiveIncluded in #{includer}" includer.module_eval do InheritedIncludedCodeBlock.call(includer) extend RecursiveIncluded end end end extend RecursiveIncluded end module Client include Feature end module Extension include Client end module Plugin include Extension end module Framework include Plugin end Client.feature Extension.feature Plugin.feature Framework.featureuber-0.1.0/test/option_test.rb0000644000175000017500000000254513137323622015354 0ustar pravipravirequire "test_helper" require "uber/option" class OptionTest < Minitest::Spec Option = Uber::Option # proc it { Option[ ->(*args) { "proc! #{args.inspect}" } ].(1,2).must_equal "proc! [1, 2]" } it { Option[ lambda { "proc!" } ].().must_equal "proc!" } # proc with instance_exec it { Option[ ->(*args) { "#{self.class} #{args.inspect}" } ].(Object, 1, 2).must_equal "OptionTest [Object, 1, 2]" } it { Option[ ->(*args) { "#{self} #{args.inspect}" }, instance_exec: true ].(Object, 1, 2).must_equal "Object [1, 2]" } # static it { Option[true].().must_equal true } it { Option[nil].().must_equal nil } it { Option[false].().must_equal false } # args are ignored. it { Option[true].(1,2,3).must_equal true } # instance method class Hello def hello(*args); "Hello! #{args.inspect}" end end it { Option[:hello].(Hello.new).must_equal "Hello! []" } it { Option[:hello].(Hello.new, 1, 2).must_equal "Hello! [1, 2]" } #--- # Callable class Callio include Uber::Callable def call(); "callable!" end end it { Option[Callio.new].().must_equal "callable!" } end # require "benchmark/ips" # method = Uber::Option[->(context, options) { context }] # proc = Uber::Option[A.new { |context, options| context }] # Benchmark.ips do |x| # x.report(:method) { method.(Object, {}) } # x.report(:proc) { proc.(Object, {}) } # end uber-0.1.0/test/builder-benchmark.rb0000644000175000017500000000070313137323622016355 0ustar pravipravirequire "test_helper" require "uber/options" require "uber/option" require "benchmark/ips" proc = ->(options) { "great" } value = Uber::Options::Value.new(proc) option = Uber::Option[proc, instance_exec: true] Benchmark.ips do |x| x.report(:value) { value.(self, {}) } x.report(:option) { option.(self, {}) } end # value 946.110k (± 2.4%) i/s - 4.766M in 5.040395s # option 1.583M (± 1.6%) i/s - 7.928M in 5.009953s uber-0.1.0/test/inheritable_attr_test.rb0000644000175000017500000000454213137323622017363 0ustar pravipravirequire 'test_helper' require "uber/inheritable_attr" class InheritableAttrTest < MiniTest::Spec describe "::inheritable_attr" do subject { Class.new(Object) do extend Uber::InheritableAttribute inheritable_attr :drinks inheritable_attr :glass inheritable_attr :guests, clone: false end } def assert_nothing_raised(*) yield end it "provides a reader with empty inherited attributes, already" do assert_equal nil, subject.drinks end it "provides a reader with empty inherited attributes in a derived class" do assert_equal nil, Class.new(subject).drinks #subject.drinks = true #Class.new(subject).drinks # TODO: crashes. end it "provides an attribute copy in subclasses" do subject.drinks = [] assert subject.drinks.object_id != Class.new(subject).drinks.object_id end it "provides a writer" do subject.drinks = [:cabernet] assert_equal [:cabernet], subject.drinks end it "inherits attributes" do subject.drinks = [:cabernet] subklass_a = Class.new(subject) subklass_a.drinks << :becks subklass_b = Class.new(subject) assert_equal [:cabernet], subject.drinks assert_equal [:cabernet, :becks], subklass_a.drinks assert_equal [:cabernet], subklass_b.drinks end it "does not inherit attributes if we set explicitely" do subject.drinks = [:cabernet] subklass = Class.new(subject) subklass.drinks = [:merlot] # we only want merlot explicitely. assert_equal [:merlot], subklass.drinks # no :cabernet, here end it "respects clone: false" do subject.guests = 2 subklass_a = Class.new(subject) subklass_b = Class.new(subject) subklass_a.guests = 13 assert_equal 2, subklass_b.guests assert_equal 13, subklass_a.guests end it "does not attempt to clone symbols" do subject.glass = :highball subklass = Class.new(subject) subklass.glass.must_equal :highball end it "does not attempt to clone true" do subject.glass = true subklass = Class.new(subject) subklass.glass.must_equal true end it "does not attempt to clone false" do subject.glass = false subklass = Class.new(subject) subklass.glass.must_equal false end end end uber-0.1.0/test/inheritance_test.rb0000644000175000017500000000217713137323622016336 0ustar pravipravi# require 'test_helper' # require 'uber/inheritable_included' # module InheritIncludedTo # def self.call(includer, proc) # proc.call(includer) # das will ich eigentlich machen # includer.class_eval do # @block = proc # def self.included(base) # # InheritIncludedTo.call(base, instance_variable_get(:@block)) # end # end # end # end # class InheritanceTest < MiniTest::Spec # module Feature # #extend Uber::InheritedIncluded # CODE_BLOCK = lambda { |base| base.class_eval { extend ClassMethods } } # i want that to be executed at every include # def self.included(includer) # # # CODE_BLOCK.call(base) # InheritIncludedTo.call(includer, CODE_BLOCK) # end # module ClassMethods # def feature; end # end # end # module Extension # include Feature # # TODO: test overriding ::included # end # module Client # include Extension # end # module ExtendedClient # include Client # end # it { Extension.must_respond_to :feature } # it { Client.must_respond_to :feature } # it { ExtendedClient.must_respond_to :feature } # end uber-0.1.0/test/test_helper.rb0000644000175000017500000000011313137323622015310 0ustar pravipravirequire 'bundler' Bundler.setup require 'minitest/autorun' require 'uber' uber-0.1.0/test/delegates_test.rb0000644000175000017500000000133413137323622015774 0ustar pravipravirequire 'test_helper' require 'uber/delegates' class DelegatesTest < MiniTest::Spec class Song extend Uber::Delegates delegates :model, :title def model Struct.new(:title).new("Helloween") end def title super.downcase end end # allows overriding in class. it { Song.new.title.must_equal "helloween" } module Title extend Uber::Delegates delegates :model, :title, :id end class Album include Title def model Struct.new(:title, :id).new("Helloween", 1) end def title super.downcase end end # allows overriding in class inherited from module. it { Album.new.title.must_equal "helloween" } it { Album.new.id.must_equal 1 } enduber-0.1.0/test/options_test.rb0000644000175000017500000000177013137323622015536 0ustar pravipravirequire 'test_helper' require 'uber/options' class UberOptionsTest < MiniTest::Spec Options = Uber::Options let (:dynamic) { Options.new(:volume =>1, :style => "Punkrock", :track => Proc.new { |i| i.to_s }) } describe "#evaluate" do it { dynamic.evaluate(Object.new, 999).must_equal({:volume =>1, :style => "Punkrock", :track => "999"}) } describe "static" do let (:static) { Options.new(:volume =>1, :style => "Punkrock") } it { static.evaluate(nil).must_equal({:volume =>1, :style => "Punkrock"}) } it "doesn't evaluate internally" do static.instance_eval do def evaluate_for(*) raise "i shouldn't be called!" end end static.evaluate(nil).must_equal({:volume =>1, :style => "Punkrock"}) end end end describe "#eval" do it { dynamic.eval(:volume, 999).must_equal 1 } it { dynamic.eval(:style, 999).must_equal "Punkrock" } it { dynamic.eval(:track, Object.new, 999).must_equal "999" } end end uber-0.1.0/test/builder_test.rb0000644000175000017500000000622613137323622015472 0ustar pravipravirequire 'test_helper' require "uber/builder" class BuilderTest < MiniTest::Spec Evergreen = Struct.new(:title) Hit = Struct.new(:title) class Song include Uber::Builder builds do |options| if options[:evergreen] Evergreen elsif options[:hit] Hit end end def self.build(options) build!(self, options).new end end # building class if no block matches it { Song.build({}).must_be_instance_of Song } it { Song.build({evergreen: true}).must_be_instance_of Evergreen } it { Song.build({hit: true}).must_be_instance_of Hit } # test chained builds. class Track include Uber::Builder builds do |options| Evergreen if options[:evergreen] end builds do |options| Hit if options[:hit] end def self.build(options) build!(self, options).new end end it { Track.build({}).must_be_instance_of Track } it { Track.build({evergreen: true}).must_be_instance_of Evergreen } it { Track.build({hit: true}).must_be_instance_of Hit } # test inheritance. builder do not inherit. class Play < Song end it { Play.build({}).must_be_instance_of Play } it { Play.build({evergreen: true}).must_be_instance_of Play } it { Play.build({hit: true}).must_be_instance_of Play } # test return from builds class Boomerang include Uber::Builder builds ->(options) do return Song if options[:hit] end def self.build(options) build!(self, options).new end end it { Boomerang.build({}).must_be_instance_of Boomerang } it { Boomerang.build({hit: true}).must_be_instance_of Song } end class BuilderScopeTest < MiniTest::Spec def self.builder_method(options) options[:from_builder_method] and return self end class Hit; end class Song class Hit end include Uber::Builder builds :builder_method # i get called first. builds ->(options) do # and i second. self::Hit end def self.build(context, options={}) build!(context, options) end end it do Song.build(self.class).must_equal BuilderScopeTest::Hit # this runs BuilderScopeTest::builder_method and returns self. Song.build(self.class, from_builder_method: true).must_equal BuilderScopeTest end class Evergreen class Hit end include Uber::Builder class << self attr_writer :builders end self.builders = Song.builders def self.build(context, options={}) build!(context, options) end def self.builder_method(options) options[:from_builder_method] and return self end end it do # running the "copied" block in Evergreen will reference the correct @context. Evergreen.build(Evergreen).must_equal BuilderScopeTest::Evergreen::Hit Evergreen.build(Evergreen, from_builder_method: true).must_equal BuilderScopeTest::Evergreen end #--- # Builders API # Builders#call # Builders#<< A = Class.new MyBuilders = Uber::Builder::Builders.new MyBuilders << ->(options) do return Song if options[:hit] end it { MyBuilders.call(A, {}).new.must_be_instance_of A } it { MyBuilders.call(A, { hit: true }).new.must_be_instance_of Song } end uber-0.1.0/CHANGES.md0000644000175000017500000000405313137323622013067 0ustar pravipravi# 0.1.0 * Introduce `Uber::Option` as a replacement for `Uber::Options::Value`. It is still there, but deprecated. * New API for `Uber::Builder`: You now add builders to `Uber::Builder::Builders` and simply call that instance while passing in context and args. This allows very simple reusable builders that can be used anywhere. * `Uber::Options` now uses `Uber::Option`. * Removing `Uber::Version` as this is done nicely by `Gem::Version`. # 0.0.15 * `Value#evaluate` is now `#call`. This will make it easier to introduce Null objects. * Options passed to `::builds` are now wrapped in `Uber::Options::Value` which allows specifying builder _class methods_ using `builds :builder_method`. * `Builder::class_builder` now accepts an optional context object which is used to `instance_exec` the builder blocks. This allows to share predefined builder blocks between different classes while resolving the constants in the respective class. # 0.0.14 * Add `inheritable_attr :title, clone: false`. Thanks to @katafrakt. * When calling `Option::Value#evaluate` with `nil` as context, and the `Value` represents a lambda, the block is called it its original context via `block.call`. # 0.0.13 * Add proc syntax for builders. This allows using `return` in the block. Thanks to @dutow for implementing this. # 0.0.12 * Make it run with Ruby 2.2. # 0.0.11 * Don't clone nil, false, true and symbols in `::inheritable_attr`. # 0.0.10 * Builders are _not_ inherited to subclasses. This allows instantiating subclasses directly without running builders. # 0.0.9 * Add `Uber::Builder`. # 0.0.8 * Add `Uber::Delegates` that provides delegation that can be overridden and called with `super`. # 0.0.7 * Add `Uber::Callable` and support for it in `Options::Value`. # 0.0.6 * Fix `Version#>=` partially. # 0.0.5 * Add `Uber::Version` for simple gem version deciders. # 0.0.4 * Fix a bug where `dynamic: true` wouldn't invoke a method but try to run it as a block. # 0.0.3 * Add `Options` and `Options::Value´ for abstracting dynamic options. # 0.0.2 * Add `::inheritable_attr`.