├── .gitignore ├── Gemfile ├── .rubocop.yml ├── examples ├── matrix.rb ├── circle.rb ├── turtle.rb ├── chain.rb ├── reg_triangle.rb └── repo.rb ├── womb.gemspec ├── lib └── womb.rb ├── LICENSE.txt ├── spec └── womb_spec.rb └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | *.gem -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in end_view.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Metrics/LineLength: 2 | Max: 100 3 | Documentation: 4 | Enabled: false 5 | Style/AlignParameters: 6 | Enabled: false -------------------------------------------------------------------------------- /examples/matrix.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/womb' 2 | 3 | Matrix = Womb[Class.new] 4 | .init(:v1, :v2) 5 | .attr_reader(:v1, :v2) 6 | .send_to_singleton(:alias_method, :[], :new) 7 | .def(:determinant) { v1[0] * v2[1] - v2[0] * v1[1] } 8 | .birth 9 | 10 | puts Matrix[[2, 2], [1, 3]].determinant # 4 11 | -------------------------------------------------------------------------------- /examples/circle.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/womb' 2 | 3 | pi = 3.14 4 | 5 | Circle = Womb[Class.new] 6 | .attr_reader(:radius) 7 | .define_method(:initialize) { |radius| @radius = radius } 8 | .define_method(:circumference) { 2 * pi * radius } 9 | .define_method(:area) { pi * radius ** 2 } 10 | .birth 11 | 12 | puts Circle.new(3).area # 28.26 13 | -------------------------------------------------------------------------------- /examples/turtle.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/womb' 2 | 3 | move = ->(dist, angle) do 4 | angle = angle * Math::PI / 180 5 | @x += dist * Math.cos(angle) 6 | @y += dist * Math.sin(angle) 7 | puts self 8 | self 9 | end 10 | 11 | Turtle = Womb[Class.new] 12 | .init(:x, :y) 13 | .def(:move, &move) 14 | .def(:to_s) { "Turtle (#{@x.round}, #{@y.round})" } 15 | .birth 16 | 17 | Turtle.new(0, 0).move(1, 0).move(2, 90) 18 | # Turtle (1, 0) 19 | # Turtle (1, 2) 20 | -------------------------------------------------------------------------------- /examples/chain.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/womb' 2 | 3 | Chain = Womb[Class.new] 4 | .init(:value) 5 | .attr_reader(:value) 6 | .send_to_singleton(:alias_method, :[], :new) 7 | .def(:>>) { |fn| @value = fn.to_proc.(value); self } 8 | .def(:<<) { |fn| fn.to_proc.(value); self } 9 | .def(:|) { |fn| fn.to_proc.(self) } 10 | .birth 11 | 12 | double = ->(v) { v * 2 } 13 | log = ->(v) { puts v } 14 | 15 | res = Chain[3] >> double >> :next << log >> double << log >> :next | :value 16 | # 7 17 | # 14 18 | puts res # 15 19 | -------------------------------------------------------------------------------- /womb.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'womb' 5 | s.version = '0.0.2' 6 | s.author = 'Max White' 7 | s.email = 'mushishi78@gmail.com' 8 | s.homepage = 'https://github.com/mushishi78/womb' 9 | s.summary = 'A Ruby library for birthing objects' 10 | s.license = 'MIT' 11 | s.files = Dir['LICENSE.txt', 'README.md', 'lib/**/*'] 12 | s.require_path = 'lib' 13 | s.add_development_dependency 'rspec', '~> 3.1', '>= 3.1.0' 14 | end 15 | -------------------------------------------------------------------------------- /examples/reg_triangle.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/womb' 2 | 3 | RegPolygon = Womb[Module.new] 4 | .assign(:interior_angle) { |sides| (sides - 2) * Math::PI / sides } 5 | .birth 6 | 7 | Triangle = Womb[Module.new] 8 | .assign(:area) { |base, height| base * height / 2 } 9 | .birth 10 | 11 | RegTriangle = Womb[Class.new] 12 | .init(:base) 13 | .def(:height) { @base * Math.sin(RegPolygon.interior_angle(3)) } 14 | .def(:area) { Triangle.area(@base, height) } 15 | .birth 16 | 17 | puts RegTriangle.new(2).area # 1.732050807568877 18 | -------------------------------------------------------------------------------- /examples/repo.rb: -------------------------------------------------------------------------------- 1 | require 'git' 2 | require 'womb' 3 | 4 | Repo = Womb[Class.new] 5 | .assign(:open) { |path| new(Git.open(path)) } 6 | .assign(:clone) { |url, path| new(Git.clone(url, path)) } 7 | .init(:git) 8 | .def(:checkout) { |branch| git.checkout(branch); self } 9 | .def(:branch) { |branch| git.branch(branch).checkout; self } 10 | .def(:add) { git.add(all: true); self } 11 | .def(:commit) { |message| git.commit(message); self } 12 | .def(:push) { git.push('origin', git.current_branch, force: true); self } 13 | .def(:ls) { add; git.ls_files.keys } 14 | .private 15 | .attr_reader(:git) 16 | .birth 17 | -------------------------------------------------------------------------------- /lib/womb.rb: -------------------------------------------------------------------------------- 1 | class Womb < BasicObject 2 | def self.[](obj) 3 | new(obj) 4 | end 5 | 6 | def initialize(obj) 7 | @obj = obj 8 | @messages = [] 9 | end 10 | 11 | def method_missing(method, *args, &b) 12 | method = aliases[method] || method 13 | return super unless obj.respond_to?(method, true) 14 | @messages << [obj, [method, *args], b] 15 | self 16 | end 17 | 18 | def init(*attributes) 19 | define_method(:initialize) do |*values| 20 | attributes.each_with_index do |attribute, i| 21 | instance_variable_set("@#{attribute}", values[i]) 22 | end 23 | end 24 | end 25 | 26 | def send_to_singleton(*args, &b) 27 | @messages << [obj.singleton_class, args, b] 28 | self 29 | end 30 | 31 | def birth 32 | messages = @messages 33 | @obj.class_eval { messages.each { |(target, args, b)| target.send(*args, &b) } } 34 | @obj 35 | end 36 | 37 | private 38 | 39 | attr_reader :obj 40 | 41 | def aliases 42 | @aliases ||= { assign: :define_singleton_method, 43 | def: :define_method } 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Max White 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/womb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'womb' 2 | 3 | describe Womb do 4 | it 'can delegate missing methods' do 5 | obj = Womb[Class.new] 6 | .attr_accessor(:a) 7 | .define_method(:square) { a ** 2 } 8 | .birth 9 | .new 10 | 11 | obj.a = 3 12 | expect(obj.square).to eql(9) 13 | end 14 | 15 | it 'can use aliased method names' do 16 | obj = Womb[Module.new] 17 | .assign(:a){5} 18 | .assign(:b){12} 19 | .birth 20 | 21 | expect(obj.a).to eql(5) 22 | expect(obj.b).to eql(12) 23 | end 24 | 25 | it 'can initialize attributes' do 26 | obj = Womb[Class.new] 27 | .init(:a, :b) 28 | .def(:add) { @a + @b } 29 | .birth 30 | .new(3, 6) 31 | 32 | expect(obj.add).to eql(9) 33 | end 34 | 35 | it 'can send messages to singleton' do 36 | klass = Womb[Class.new] 37 | .init(:a) 38 | .assign(:build) { |a| new(a.to_s) } 39 | .send_to_singleton(:alias_method, :[], :build) 40 | .attr_reader(:a) 41 | .birth 42 | 43 | expect(klass.build(7).a).to eql('7') 44 | expect(klass[7].a).to eql('7') 45 | end 46 | 47 | it 'can use private for postceding definitions' do 48 | obj = Womb[Class.new] 49 | .private 50 | .def(:a){5} 51 | .birth 52 | .new 53 | 54 | expect { obj.a }.to raise_error(NoMethodError) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Womb 2 | 3 | `Womb` wraps around an object to present a chainable interface for defining 4 | objects. It also provides short aliases for longer method names and helpers to 5 | reduce boiler plate. 6 | 7 | ## Usage 8 | 9 | Once wrapped around an object, `Womb` stores all messages via `method_missing` 10 | and then sends them to the object when the `birth` method is called, returning 11 | the defined object. 12 | 13 | ``` ruby 14 | require 'womb' 15 | 16 | pi = 3.14 17 | 18 | Circle = Womb[Class.new] 19 | .attr_reader(:radius) 20 | .define_method(:initialize) { |radius| @radius = radius } 21 | .define_method(:circumference) { 2 * pi * radius } 22 | .define_method(:area) { pi * radius ** 2 } 23 | .birth 24 | 25 | Circle.new(3).area # 28.26 26 | ``` 27 | 28 | For shorthand, `def` and `assign` can be used as aliases for `define_method` 29 | and `define_singleton_method` respectively, and `init` can be used to create 30 | an `initialize` method that simply sets instance variables from it's arguments. 31 | 32 | ``` ruby 33 | require 'git' 34 | 35 | Repo = Womb[Class.new] 36 | .assign(:open) { |path| new(Git.open(path)) } 37 | .assign(:clone) { |url, path| new(Git.clone(url, path)) } 38 | .init(:git) 39 | .def(:checkout) { |branch| git.checkout(branch); self } 40 | .def(:branch) { |branch| git.branch(branch).checkout; self } 41 | .def(:add) { git.add(all: true); self } 42 | .def(:commit) { |message| git.commit(message); self } 43 | .def(:push) { git.push('origin', git.current_branch, force: true); self } 44 | .def(:ls) { add; git.ls_files.keys } 45 | .private 46 | .attr_reader(:git) 47 | .birth 48 | 49 | Repo.open('../my_local_repo').add.commit('Updating with changes').push 50 | ``` 51 | 52 | An additional helper, `send_to_singleton`, can be used to send messages to the 53 | object's singleton class. 54 | 55 | ``` ruby 56 | Matrix = Womb[Class.new] 57 | .init(:v1, :v2) 58 | .attr_reader(:v1, :v2) 59 | .send_to_singleton(:alias_method, :[], :new) 60 | .def(:determinant) { v1[0] * v2[1] - v2[0] * v1[1] } 61 | .birth 62 | 63 | Matrix[[2, 2], [1, 3]].determinant # 4 64 | ``` 65 | 66 | If you prefer not to define a method with a block directly, it can defined as 67 | a lambda and passed in using the `to_proc` operator, `&`. The objects private 68 | state can still be accessed with instance variables. 69 | 70 | ``` ruby 71 | move = ->(dist, angle) do 72 | angle = angle * Math::PI / 180 73 | @x += dist * Math.cos(angle) 74 | @y += dist * Math.sin(angle) 75 | puts self 76 | self 77 | end 78 | 79 | Turtle = Womb[Class.new] 80 | .init(:x, :y) 81 | .def(:move, &move) 82 | .def(:to_s) { "Turtle (#{@x.round}, #{@y.round})" } 83 | .birth 84 | 85 | Turtle.new(0, 0).move(1, 0).move(2, 90) 86 | # Turtle (1, 0) 87 | # Turtle (1, 2) 88 | ``` 89 | 90 | ## Installation 91 | 92 | Add your Gemfile: 93 | 94 | ```ruby 95 | gem 'womb' 96 | ``` 97 | 98 | ## Contributing 99 | 100 | 1. [Fork it]( https://github.com/mushishi78/womb/fork) 101 | 2. Create your feature branch (`git checkout -b my-new-feature`) 102 | 3. Commit your changes (`git commit -am 'Add some feature'`) 103 | 4. Push to the branch (`git push origin my-new-feature`) 104 | 5. Create a new Pull Request 105 | --------------------------------------------------------------------------------