Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first version

  • Loading branch information...
commit 799fd1c8084a5ebe9c38be48c615edbbd334c3f1 0 parents
@mkristian authored
1  .gitignore
@@ -0,0 +1 @@
+playground
17 babel.gemspec
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+Gem::Specification.new do |s|
+ s.name = 'babel'
+ s.version = '0.1'
+
+ s.summary = 'babel offers a filter for hashes and with that comes json/yaml/xml de/serialization of models which provides a hash representation'
+
+ s.authors = ['Kristian Meier']
+ s.email = ['m.kristian@web.de']
+
+
+ s.files += Dir['lib/**/*']
+ s.files += Dir['spec/**/*']
+ s.test_files += Dir['spec/**/*_spec.rb']
+ #s.add_development_dependency 'virtus', '~>0.9.0'
+ s.add_development_dependency 'minitest', '2.11.3'
+end
2  lib/babel.rb
@@ -0,0 +1,2 @@
+require 'babel/serializer'
+require 'babel/deserializer'
63 lib/babel/deserializer.rb
@@ -0,0 +1,63 @@
+module Babel
+ class Deserializer
+
+ def initialize(model_class)
+ @model_class = model_class
+ end
+
+ private
+
+ def self.filter
+ @filter ||= HashFilter.new
+ end
+
+ def filter
+ @filter ||= self.class.filter.dup
+ end
+
+ protected
+
+ def self.default_context_key(default)
+ filter.default_context_key(default)
+ end
+
+ def self.add_context(key, options = {})
+ filter[key] = options
+ end
+
+ public
+
+ def use(context_or_options)
+ filter.use(context_or_options)
+ self
+ end
+
+ def from_hash(data, options = nil)
+ filter.use(options) if options
+ if root = filter.options[:root]
+ if data.is_a? Array
+ root = root.to_s
+ data.collect{ |d| @model_class.new(filter.filter(d[root])) }
+ else
+ @model_class.new(filter.filter(data[root.to_s]))
+ end
+ else
+ if data.is_a? Array
+ data.collect{ |d| @model_class.new(filter.filter(d)) }
+ else
+ @model_class.new(filter.filter(data))
+ end
+ end
+ end
+
+ def from_json(json, options = nil)
+ data = JSON.parse(json)
+ from_hash(data, options)
+ end
+
+ def from_yaml(yaml, options = nil)
+ data = YAML.load_stream(StringIO.new(yaml)).documents
+ from_hash(data, options)
+ end
+ end
+end
126 lib/babel/hash_filter.rb
@@ -0,0 +1,126 @@
+module Babel
+ class HashFilter
+
+ def initialize(context_or_options = nil)
+ use(context_or_options)
+ end
+
+ private
+
+ def context
+ @context ||= {}
+ end
+
+ public
+
+ def default_context_key(default = nil)
+ @default = default if default
+ @default
+ end
+
+ def []=(key, options)
+ context[key.to_sym] = options if key
+ end
+
+ def [](key)
+ context[key.to_sym] if key
+ end
+
+ def use(context_or_options)
+ if context_or_options
+ case context_or_options
+ when Symbol
+ if opts = context[context_or_options]
+ @options = opts.dup
+ end
+ when Hash
+ @options = context_or_options
+ end
+ else
+ @options = nil
+ end
+ self
+ end
+
+ NO_MODEL = Object.new
+ def NO_MODEL.send(*args)
+ self
+ end
+
+ def filter(hash = {}, model = NO_MODEL, &block)
+ filter_data(model, hash, options, &block) if hash
+ end
+
+ def options
+ @options || context[default_context_key] || {}
+ end
+
+ private
+
+ def filter_data(model, data, options = {}, &block)
+ only = options[:only].collect { |o| o.to_s } if options[:only]
+ except = (options[:except] || []).collect { |e| e.to_s }
+
+ include =
+ case options[:include]
+ when Array
+ options[:include].collect { |i| i.to_s }
+ when Hash
+ Hash[options[:include].collect {|k,v| [k.to_s, v]}]
+ else
+ []
+ end
+ if model != NO_MODEL
+ methods = (options[:methods] || []).collect { |e| e.to_s }
+ methods.each do |m|
+ data[m] = model.send(m.to_sym)
+ end
+
+ include_methods = include.is_a?(Array) ? include : include.keys
+ include_methods.each do |m|
+ unless data.include?(m)
+ raise "no block given to calculate the attributes from model" unless block
+ models_or_model = model.send(m)
+ if models_or_model.respond_to?(:collect)
+ data[m] = models_or_model.collect { |i| block.call(i) }
+ else
+ data[m]= block.call(model.send(m))
+ end
+ end
+ end
+ end
+ methods ||= []
+
+ result = {}
+ data.each do |k,v|
+ case v
+ when Hash
+ if include.include?(k)
+ case include
+ when Array
+ result[k] = filter_data(model.send(k), v, &block)
+ when Hash
+ result[k] = filter_data(model.send(k), v, include[k], &block)
+ end
+ end
+ when Array
+ if include.include?(k)
+ models = model.send(k)
+ case include
+ when Array
+ result[k] = v.enum_with_index.collect { |i, j| filter_data(models[j], i, &block) }
+ when Hash
+ opts = include[k]
+ result[k] = v.enum_with_index.collect { |i, j| filter_data(models[j], i, opts, &block) }
+ end
+ end
+ else
+ if methods.include?(k) || (only && only.include?(k)) || (only.nil? && !except.include?(k))
+ result[k] = v
+ end
+ end
+ end
+ result
+ end
+ end
+end
102 lib/babel/serializer.rb
@@ -0,0 +1,102 @@
+require 'babel/hash_filter'
+module Babel
+ class Serializer
+
+ def initialize(model_or_models)
+ @model_or_models = model_or_models
+ end
+
+ def respond_to?(method)
+ @model_or_models.respond_to?(method)
+ end
+
+ def method_missing(method, *args, &block)
+ @model_or_models.send(method, *args, &block)
+ end
+
+ private
+
+ def self.filter
+ @filter ||= HashFilter.new
+ end
+
+ def filter
+ @filter ||= self.class.filter.dup
+ end
+
+ protected
+
+ # for rails
+ def self.model(model = nil)
+ @model_class = model if model
+ @model_class ||= self.to_s.sub(/Serializer$/, '').constantize
+ end
+
+ # for rails
+ def self.model_name
+ model.model_name
+ end
+
+ def self.default_context_key(default)
+ filter.default_context_key(default)
+ end
+
+ def self.add_context(key, options = {})
+ filter[key] = options
+ end
+
+ public
+
+ def use(context_or_options)
+ filter.use(context_or_options)
+ self
+ end
+
+ def to_hash(options = nil)
+ filter.use(options) if options
+ case @model_or_models
+ when Array
+ @model_or_models.collect do |m|
+ filter_model(attr(m), m)
+ end
+ else
+ filter_model(attr(@model_or_models), @model_or_models)
+ end
+ end
+
+ def to_json(options = nil)
+ to_hash(options).to_json
+ end
+
+ def to_xml(options = nil)
+ opts = fitler.options.dup
+ root = opts.delete :root
+ fitler.use(opts)
+ result = to_hash
+ if root && result.is_a?(Array) && root.respond_to?(:pluralize)
+ root = root.to_s.pluralize
+ end
+ result.to_xml :root => root
+ end
+
+ def to_yaml(options = nil)
+ to_hash(options).to_yaml
+ end
+
+ protected
+
+ def attr(model)
+ model.attributes if model
+ end
+
+ private
+
+ def filter_model(model, data)
+ if root = filter.options[:root]
+ {root.to_s => filter.filter(model, data){ |model| attr(model) } }
+ else
+ filter.filter(model, data){ |model| attr(model) }
+ end
+ end
+ end
+end
97 spec/filter_spec.rb
@@ -0,0 +1,97 @@
+$LOAD_PATH << File.dirname(__FILE__)
+
+require 'spec_helper'
+
+class Hash
+ def attributes
+ self
+ end
+ def method_missing(method)
+ self[method.to_s]
+ end
+end
+
+describe Babel do
+ let(:data) do
+ data = {
+ 'id' => 987,
+ 'name' => 'me and the corner',
+ 'address' => { 'street' => 'Foo 12', 'zipcode' => '12345' },
+ 'phone_numbers' => {
+ 'prefix' => 12,
+ 'number' => '123',
+ 'area' => { 'code' => '001', 'iso' => 'us'}
+ }
+ }
+ class Hash
+ def self.new(hash)
+ self[hash]
+ end
+ end
+ data
+ end
+
+ let(:serializer) { Babel::Serializer.new(data) }
+ let(:deserializer) { Babel::Deserializer.new(Hash) }
+
+ it 'should serialize and deserialize a hash' do
+ json = serializer.to_json
+ result = deserializer.from_json(json)
+ result.must_equal Hash['id' => data['id'], 'name' => data['name']]
+ end
+
+ it 'should serialize and deserialize a hash with root' do
+ json = serializer.to_json :root => :my
+ result = deserializer.from_json(json, :root => :my)
+ result.must_equal Hash['id' => data['id'], 'name' => data['name']]
+ end
+
+ it 'should serialize and deserialize a hash with include list' do
+ json = serializer.to_json(:include => ['address', 'phone_numbers'])
+ result = deserializer.from_json(json, :include => ['address', 'phone_numbers'])
+ data['phone_numbers'].delete('area')
+ result.must_equal Hash[data]
+ end
+
+ it 'should serialize and deserialize a hash with except' do
+ json = serializer.to_json(:except => ['id'])
+ result = deserializer.from_json(json, :except => ['id'])
+ result.must_equal Hash['name' => data['name']]
+ result = deserializer.from_json(json)
+ result.must_equal Hash['name' => data['name']]
+ end
+
+ it 'should serialize and deserialize a hash with only' do
+ json = serializer.to_json(:only => ['name'])
+ result = deserializer.from_json(json, :only => ['name'])
+ result.must_equal Hash['name' => data['name']]
+ result = deserializer.from_json(json)
+ result.must_equal Hash['name' => data['name']]
+ end
+
+ it 'should serialize and deserialize a hash with nested only' do
+ json = serializer.to_json(:include => { 'address' => {:only => ['street']}})
+ data.delete('phone_numbers')
+ data['address'].delete('zipcode')
+ result = deserializer.from_json(json, :include => { 'address' => {:only => ['street']}})
+ result.must_equal data
+ result = deserializer.from_json(json, :include => ['address'])
+ result.must_equal data
+ end
+
+ it 'should serialize and deserialize a hash with nested except' do
+ json = serializer.to_json(:include => { 'address' => {:except => ['zipcode']}})
+ data.delete('phone_numbers')
+ data['address'].delete('zipcode')
+ result = deserializer.from_json(json, :include => { 'address' => {:except => ['zipcode']}})
+ result.must_equal data
+ result = deserializer.from_json(json, :include => ['address'])
+ result.must_equal data
+ end
+
+ it 'should serialize and deserialize a hash with nested include' do
+ json = serializer.to_json(:include => { 'address' => {}, 'phone_numbers' => { :include => ['area']}})
+ result = deserializer.from_json(json, :include => { 'address' => {}, 'phone_numbers' => { :include => ['area']}})
+ result.must_equal data
+ end
+end
4 spec/spec_helper.rb
@@ -0,0 +1,4 @@
+$LOAD_PATH << File.expand_path(File.join(__FILE__, '..', '..', 'lib'))
+require 'minitest/autorun'
+require 'json'
+require 'babel'
Please sign in to comment.
Something went wrong with that request. Please try again.