hard-boiled 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in hard-boiled.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec'
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ ## HardBoiled
2
+
3
+ simply define mapping from you model to a simple hash. For those who worked with [thoughtbot](http://thoughtbot.com)'s [factory girl](http://github.com/thoughtbot/factory_girl) the DSL should be familiar.
4
+
5
+ ### Installation
6
+
7
+ gem install hard-boiled
8
+
9
+ ### Usage
10
+
11
+ ```ruby
12
+ require 'hard-boiled'
13
+
14
+ egg = OpenStruct.new({
15
+ :boil_time => 7,
16
+ :temperature => 99,
17
+ :colour => "beige"
18
+ })
19
+
20
+ HardBoiled::Presenter.define egg do
21
+ time :from => :boil_time
22
+ colour
23
+ temperature :format => "%d ℃"
24
+ end # => { :time => 7, :temperature => "99 ℃", :colour => "beige" }
25
+ ```
26
+
27
+ for more examples see the tests in the `spec` directory.
28
+
29
+ ### Similar Projects
30
+
31
+ If _hard-boiled_ isn't your cup of tea, go and check out other ways to map models
32
+ to hashes (for data serialization):
33
+
34
+ * [Representative](https://github.com/mdub/representative)
35
+ * [Tokamak](https://github.com/abril/tokamak)
36
+ * [Builder](http://rubygems.org/gems/builder)
37
+ * [JSONify](https://github.com/bsiggelkow/jsonify)
38
+ * [Argonaut](https://github.com/jbr/argonaut)
39
+ * [JSON Builder](https://github.com/dewski/json_builder)
40
+ * [RABL](https://github.com/nesquena/rabl)
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "hard-boiled/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "hard-boiled"
7
+ s.version = Hard::Boiled::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Lennart Melzer"]
10
+ s.email = ["me@lmaa.name"]
11
+ s.homepage = ""
12
+ s.summary = %q{Get your models boiled down to plain hashes!}
13
+ s.description = %q{
14
+ HardBoiled helps you reducing your complex models (including their associations)
15
+ down to simple hashes usable for serialization into JSON or XML.
16
+
17
+ It leverages a DSL similar to thoughtbot's FactoryGirl
18
+ to make mappings maintainable and pain-free.
19
+ }
20
+
21
+ s.rubyforge_project = "hard-boiled"
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,3 @@
1
+ module HardBoiled
2
+ autoload :Presenter, 'hard-boiled/presenter'
3
+ end
@@ -0,0 +1,32 @@
1
+ # Somebody said, I won't depend on `ActiveSupport` for one method. Ok
2
+ # these are two but still… taken from the
3
+ # [rails project](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/array/extract_options.rb)
4
+ class Hash
5
+ # By default, only instances of Hash itself are extractable.
6
+ # Subclasses of Hash may implement this method and return
7
+ # true to declare themselves as extractable. If a Hash
8
+ # is extractable, Array#extract_options! pops it from
9
+ # the Array when it is the last element of the Array.
10
+ def extractable_options?
11
+ instance_of?(Hash)
12
+ end
13
+ end
14
+
15
+ class Array
16
+ # Extracts options from a set of arguments. Removes and returns the last
17
+ # element in the array if it's a hash, otherwise returns a blank hash.
18
+ #
19
+ # def options(*args)
20
+ # args.extract_options!
21
+ # end
22
+ #
23
+ # options(1, 2) # => {}
24
+ # options(1, 2, :a => :b) # => {:a=>:b}
25
+ def extract_options!
26
+ if last.is_a?(Hash) && last.extractable_options?
27
+ pop
28
+ else
29
+ {}
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,67 @@
1
+ module HardBoiled
2
+ require File.dirname(__FILE__)+'/extract_options' unless {}.respond_to?(:extractable_options?)
3
+
4
+ # This class pretty much resembles what Thoughtbot did in
5
+ # [FactoryGirl's DefinitionProxy](https://github.com/thoughtbot/factory_girl/blob/master/lib/factory_girl/definition_proxy.rb)
6
+ # although it just reduces a `class` to a simple `Hash`
7
+ class Presenter
8
+ class MissingFilterError < StandardError; end
9
+ UNPROXIED_METHODS = %w(__send__ __id__ nil? respond_to? class send object_id extend instance_eval initialize block_given? raise)
10
+
11
+ (instance_methods + private_instance_methods).each do |m|
12
+ undef_method m unless UNPROXIED_METHODS.include? m
13
+ end
14
+
15
+ attr_reader :subject, :parent_subject
16
+
17
+ def self.define object, parent = nil, &block
18
+ new(object, parent).
19
+ instance_eval(&block).
20
+ to_hash
21
+ end
22
+
23
+ def initialize subject, parent = nil
24
+ @subject = subject
25
+ @parent_subject = parent
26
+ @hash = {}
27
+ end
28
+
29
+ def to_hash
30
+ @hash
31
+ end
32
+
33
+ private
34
+ def method_missing id, *args, &block
35
+ options = args.extract_options!
36
+ value = options[:nil] ? nil : (args.shift || (options[:parent] ? parent_subject : subject).__send__(options[:from] || id))
37
+ @hash[id] =
38
+ if block_given?
39
+ if value.kind_of? Array
40
+ value.map do |v|
41
+ self.class.define(v, self.subject, &block)
42
+ end
43
+ else
44
+ self.class.define(value, self.subject, &block)
45
+ end
46
+ else
47
+ __format_value __apply_filters(value, options), options
48
+ end
49
+ self
50
+ end
51
+
52
+ def __apply_filters value, options
53
+ if filters = options[:filters]
54
+ filters.inject(value) { |result, filter|
55
+ raise MissingFilterError unless self.respond_to?(filter)
56
+ self.__send__(filter, result)
57
+ }
58
+ else
59
+ value
60
+ end
61
+ end
62
+
63
+ def __format_value value, options
64
+ (format = options[:format]) ? format % value : value
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,5 @@
1
+ module Hard
2
+ module Boiled
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module MyFilters
4
+ def upcase value
5
+ value.upcase
6
+ end
7
+
8
+ def twice_and_a_half value
9
+ value * 2.5
10
+ end
11
+ end
12
+
13
+ class Filterable < HardBoiled::Presenter
14
+ include MyFilters
15
+ end
16
+
17
+ describe HardBoiled::Presenter do
18
+ let(:egg) {
19
+ OpenStruct.new({:temperature => 25, :boil_time => 7, :colour => "white"})
20
+ }
21
+
22
+ it "should produce correct hash" do
23
+ definition = described_class.define egg do
24
+ colour
25
+ time :from => :boil_time
26
+ consumer "Lennart"
27
+ end
28
+
29
+ definition.should == {
30
+ :colour => "white",
31
+ :time => 7,
32
+ :consumer => "Lennart"
33
+ }
34
+ end
35
+
36
+ context :nested do
37
+ let(:egg_box) {
38
+ OpenStruct.new({
39
+ :eggs => [egg],
40
+ :flavour => "extra tasty",
41
+ :packaged_at => "2011-11-22"
42
+ })
43
+ }
44
+
45
+ it "should allow nested objects" do
46
+ definition = Filterable.define egg_box do
47
+ contents :from => :eggs do
48
+ colour
49
+ time :from => :boil_time, :filters => [:twice_and_a_half], :format => "%.2f minutes"
50
+ taste :from => :flavour, :parent => true
51
+ consumer "Lennart", :filters => [:upcase]
52
+ end
53
+
54
+ date :from => :packaged_at, :format => "on %s"
55
+ end
56
+
57
+ definition.should == {
58
+ :contents => [
59
+ {
60
+ :colour => "white",
61
+ :time => "17.50 minutes",
62
+ :consumer => "LENNART",
63
+ :taste => "extra tasty"
64
+ }
65
+ ],
66
+ :date => "on 2011-11-22"
67
+ }
68
+ end
69
+ end
70
+
71
+ context :filtering do
72
+ it "should apply filters" do
73
+ definition = Filterable.define egg do
74
+ colour :filters => [:upcase]
75
+ time :from => :boil_time
76
+ consumer "Lennart"
77
+ end
78
+
79
+ definition.should == {
80
+ :colour => "WHITE",
81
+ :time => 7,
82
+ :consumer => "Lennart"
83
+ }
84
+ end
85
+
86
+ it "should raise on missing filter" do
87
+ expect {
88
+ definition = described_class.define egg do
89
+ colour :filters => [:upcase]
90
+ time :from => :boil_time
91
+ consumer "Lennart"
92
+ end
93
+ }.to raise_error(HardBoiled::Presenter::MissingFilterError)
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'hard-boiled'
5
+
6
+ require 'ostruct'
7
+
8
+ RSpec.configure do |config|
9
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hard-boiled
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Lennart Melzer
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-11-23 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: "\n HardBoiled helps you reducing your complex models (including their associations)\n down to simple hashes usable for serialization into JSON or XML.\n\n It leverages a DSL similar to thoughtbot's FactoryGirl \n to make mappings maintainable and pain-free.\n "
23
+ email:
24
+ - me@lmaa.name
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - README.md
35
+ - Rakefile
36
+ - hard-boiled.gemspec
37
+ - lib/hard-boiled.rb
38
+ - lib/hard-boiled/extract_options.rb
39
+ - lib/hard-boiled/presenter.rb
40
+ - lib/hard-boiled/version.rb
41
+ - spec/presenter_spec.rb
42
+ - spec/spec_helper.rb
43
+ has_rdoc: true
44
+ homepage: ""
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project: hard-boiled
73
+ rubygems_version: 1.4.2
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Get your models boiled down to plain hashes!
77
+ test_files:
78
+ - spec/presenter_spec.rb
79
+ - spec/spec_helper.rb