elastic_attributes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest ADDED
@@ -0,0 +1,5 @@
1
+ Manifest
2
+ README.rdoc
3
+ Rakefile
4
+ lib/elastic_attributes.rb
5
+ spec/elastic_attributes_spec.rb
data/README.rdoc ADDED
@@ -0,0 +1,43 @@
1
+ = Elastic attributes
2
+
3
+ Flexible attribute mapper. Input any ruby structure, map sub-structures to specified classes.
4
+
5
+ Can be used to map decoded JSON structures to objects, e.g. with document-oriented databases or JSON APIs.
6
+
7
+ == Examples
8
+
9
+ class Person
10
+ include ElasticAttributes
11
+ attribute :name, :is_default => true
12
+ end
13
+
14
+ class City
15
+ include ElasticAttributes
16
+ attribute :name, :is_default => true
17
+ attribute :mayor, Person
18
+ end
19
+
20
+ class Country
21
+ include ElasticAttributes
22
+ attribute :name, :is_default => true
23
+ attribute :cities, [Array, City] # Array of Cities
24
+ end
25
+
26
+ Country.from( 'Hungary' )
27
+ Country.from( {'name' => 'Hungary'} )
28
+ Country.from( {'name' => 'Hungary', 'cities' => ['Budapest', 'Miskolc', 'Debrecen']} )
29
+ Country.from( {'name' => 'Hungary', 'cities' => [{'name' => 'Budapest', 'mayor' => 'Demszky Gabor'},
30
+ 'Miskolc',
31
+ 'Debrecen']} )
32
+ City.from( {'name' => 'Budapest', 'mayor' => 'Demszky Gabor'} )
33
+ city = City.from( {'name' => 'Budapest', 'mayor' => {'name' => 'Demszky Gabor'}} )
34
+
35
+ city.encode # => {"name"=>"Budapest", "mayor"=>"Demszky Gabor"}
36
+
37
+ == Installation
38
+
39
+ gem install elastic_attributes
40
+
41
+ == License
42
+
43
+ http://sam.zoy.org/wtfpl/
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('elastic_attributes', '0.1.0') do |p|
6
+ p.description = "Flexible attribute mapping"
7
+ p.url = "http://github.com/bagilevi/elastic_attributes"
8
+ p.author = "Levente Bagi"
9
+ p.email = "bagilevi@gmail.com"
10
+ p.ignore_pattern = []
11
+ p.development_dependencies = []
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{elastic_attributes}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Levente Bagi"]
9
+ s.date = %q{2010-09-26}
10
+ s.description = %q{Flexible attribute mapping}
11
+ s.email = %q{bagilevi@gmail.com}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/elastic_attributes.rb"]
13
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "lib/elastic_attributes.rb", "spec/elastic_attributes_spec.rb", "elastic_attributes.gemspec"]
14
+ s.homepage = %q{http://github.com/bagilevi/elastic_attributes}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Elastic_attributes", "--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{elastic_attributes}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Flexible attribute mapping}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
@@ -0,0 +1,116 @@
1
+ module ElasticAttributes
2
+
3
+ module ClassMethods
4
+
5
+ # Define an attribute in the current class.
6
+ # attribute :image, Image # Image.from(data) will be called on the input['image']
7
+ # attribute :images, [Array, Image] # array of images - Image.from(data) will be called on all items in input['images'] array
8
+ # attribute :text, :is_default => true # If the input is not a hash, it will be assigned to this attribute, and all other attributes will be nil
9
+ def attribute(name, type_or_options = nil, options = {})
10
+ if type_or_options.is_a?(Hash)
11
+ options = type_or_options
12
+ else
13
+ options[:type] = type_or_options if type_or_options
14
+ end
15
+ attr_accessor name
16
+ self.attributes ||= {}
17
+ self.attributes[name] = options
18
+ self.default_attribute = name if options[:is_default]
19
+ end
20
+
21
+ # Create an object from data
22
+ def from data
23
+ obj = new
24
+ obj.decode data
25
+ obj
26
+ end
27
+
28
+ end
29
+
30
+ # Unserialize data: map it to the current object attributes.
31
+ def decode data
32
+ if data.is_a? Hash
33
+ self.class.attributes.each do |name, options|
34
+ send("#{name}=", processed_data(data[name] || data[name.to_s], options))
35
+ end
36
+ elsif name = self.class.default_attribute
37
+ options = self.class.attributes[name]
38
+ send("#{name}=", processed_data(data, options))
39
+ else
40
+ raise ArgumentError.new("data is not a Hash (it\'s a #{data.class}) and default attribute not specified")
41
+ end
42
+ end
43
+
44
+ # Serialize data
45
+ def encode
46
+ if (name = self.class.default_attribute) && ! (self.class.attributes.keys - [name]).any?{|n|send(n)}
47
+ send(name)
48
+ else
49
+ Hash[(
50
+ self.class.attributes.keys.map do |name|
51
+ value = encoded_data(send(name), self.class.attributes[name])
52
+ [name.to_s, value] if value
53
+ end.compact
54
+ )]
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def processed_data(data, options)
61
+ if data.nil?
62
+ nil
63
+ elsif options[:type]
64
+ types = Array(options[:type])
65
+ main_type = types.first
66
+ if main_type == Array && types.size > 1
67
+ data.map{|data_item| types[1].from data_item }
68
+ elsif main_type.respond_to? :from
69
+ main_type.from data
70
+ elsif main_type == Time
71
+ require 'time'
72
+ Time.parse(data)
73
+ elsif main_type == Hash
74
+ Hash[data]
75
+ else
76
+ main_type.new data
77
+ end
78
+ else
79
+ data
80
+ end
81
+ end
82
+
83
+ def encoded_data(data, options)
84
+ if data.nil?
85
+ nil
86
+ elsif options[:type]
87
+ types = Array(options[:type])
88
+ main_type = types.first
89
+ if main_type == Array && types.size > 1
90
+ data.map{|data_item| data_item.encode }
91
+ elsif data.respond_to? :encode
92
+ data.encode
93
+ elsif main_type == Time
94
+ data.to_s
95
+ elsif main_type == Hash
96
+ data
97
+ else
98
+ data
99
+ end
100
+ else
101
+ data
102
+ end
103
+ end
104
+
105
+ class << self
106
+ def included(klass)
107
+ klass.instance_eval do
108
+ # cattr_accessible
109
+ [:attributes, :default_attribute].each {|n|instance_eval"def #{n};@#{n};end; def #{n}=(v);@#{n}=v;end" }
110
+ end
111
+ klass.extend ElasticAttributes::ClassMethods
112
+ end
113
+ end
114
+
115
+ end
116
+
@@ -0,0 +1,130 @@
1
+ require 'rspec'
2
+ require File.dirname(__FILE__) + '/../lib/elastic_attributes'
3
+
4
+ class Person
5
+ include ElasticAttributes
6
+ attribute :name
7
+ end
8
+
9
+ class City
10
+ include ElasticAttributes
11
+ attribute :name
12
+ attribute :mayor, Person
13
+ end
14
+
15
+ class Country
16
+ include ElasticAttributes
17
+ attribute :name
18
+ attribute :cities, [Array, City] # Array of Cities
19
+ end
20
+
21
+ class Item
22
+ include ElasticAttributes
23
+ attribute :description, :is_default => true
24
+ attribute :notes
25
+ end
26
+
27
+ class List
28
+ include ElasticAttributes
29
+ attribute :items, [Array, Item]
30
+ end
31
+
32
+ class Apple
33
+ include ElasticAttributes
34
+ attribute :picked_at, Time
35
+ end
36
+
37
+ class Collection
38
+ include ElasticAttributes
39
+ attribute :things, Array
40
+ end
41
+
42
+ class House
43
+ include ElasticAttributes
44
+ attribute :tenants, Hash
45
+ end
46
+
47
+
48
+ describe ElasticAttributes do
49
+
50
+ describe "decoding" do
51
+
52
+ it "should handle simple attribute" do
53
+ person = Person.from({'name' => 'Andrea'})
54
+ person.name.should == 'Andrea'
55
+ end
56
+
57
+ it "should create an object with the given type" do
58
+ city = City.from({'name' => 'Budapest', 'mayor' => {'name' => 'Gábor Demszky'}})
59
+ city.name.should == 'Budapest'
60
+ city.mayor.should be_a Person
61
+ city.mayor.name.should == 'Gábor Demszky'
62
+ end
63
+
64
+ it "should create an object with missing custom-typed attribute" do
65
+ city = City.from({'name' => 'Budapest'})
66
+ city.name.should == 'Budapest'
67
+ city.mayor.should be_nil
68
+ end
69
+
70
+ it "should create an array of objects with a given type" do
71
+ country = Country.from({'name' => 'Hungary', 'cities' => [{'name' => 'Budapest'}, {'name' => 'Miskolc'}]})
72
+ country.cities.each{|city| city.should be_a City}
73
+ country.cities.first.name.should == 'Budapest'
74
+ end
75
+
76
+ it "should use default attribute if the source data is not a hash" do
77
+ list = List.from({'items' => ['buy milk',
78
+ 'feed cat',
79
+ {'description' => 'water plants', 'notes' => 'in the room too'}
80
+ ]})
81
+ list.items.each{|item| item.should be_a Item}
82
+ list.items.first.description.should == 'buy milk'
83
+ list.items.first.notes.should be_nil
84
+ list.items.last.description.should == 'water plants'
85
+ list.items.last.notes.should == 'in the room too'
86
+ end
87
+
88
+ it "should handle Time" do
89
+ apple = Apple.from({'picked_at' => '2010-09-25 15:15'})
90
+ apple.picked_at.should == Time.parse('2010-09-25 15:15')
91
+ end
92
+
93
+ it "should handle Array" do
94
+ a = ['foo', :bar, {:name => 'John'}]
95
+ collection = Collection.from({'things' => a})
96
+ collection.things.should == a
97
+ end
98
+
99
+ it "should handle Hash" do
100
+ h = {:basement => 'John', :floor1 => 'Mary'}
101
+ house = House.from({'tenants' => h})
102
+ house.tenants.should == h
103
+ end
104
+ end
105
+
106
+ describe "encoding" do
107
+ it "should be the reverse of decoding" do
108
+ [
109
+ [Person, {'name' => 'Andrea'}],
110
+ [City, {'name' => 'Budapest', 'mayor' => {'name' => 'Gábor Demszky'}}],
111
+ [City, {'name' => 'Budapest'}],
112
+ [Country, {'name' => 'Hungary', 'cities' => [{'name' => 'Budapest'}, {'name' => 'Miskolc'}]}],
113
+ [List, {'items' => ['buy milk',
114
+ 'feed cat',
115
+ {'description' => 'water plants', 'notes' => 'in the room too'}
116
+ ]}],
117
+ [Collection, {'things' => ['foo', :bar, {:name => 'John'}]}],
118
+ [House, {'tenants' => {:basement => 'John', :floor1 => 'Mary'}}]
119
+ ].each do |klass, input|
120
+ klass.from(input).encode.should == input
121
+ end
122
+ end
123
+
124
+ it "should encode time" do
125
+ apple = Apple.from({'picked_at' => '2010-09-25 15:15'})
126
+ apple.encode['picked_at'].should be_a String
127
+ Time.parse(apple.encode['picked_at']).should == Time.parse('2010-09-25 15:15')
128
+ end
129
+ end
130
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elastic_attributes
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Levente Bagi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-26 00:00:00 +03:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Flexible attribute mapping
23
+ email: bagilevi@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.rdoc
30
+ - lib/elastic_attributes.rb
31
+ files:
32
+ - Manifest
33
+ - README.rdoc
34
+ - Rakefile
35
+ - lib/elastic_attributes.rb
36
+ - spec/elastic_attributes_spec.rb
37
+ - elastic_attributes.gemspec
38
+ has_rdoc: true
39
+ homepage: http://github.com/bagilevi/elastic_attributes
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --line-numbers
45
+ - --inline-source
46
+ - --title
47
+ - Elastic_attributes
48
+ - --main
49
+ - README.rdoc
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: 11
67
+ segments:
68
+ - 1
69
+ - 2
70
+ version: "1.2"
71
+ requirements: []
72
+
73
+ rubyforge_project: elastic_attributes
74
+ rubygems_version: 1.3.7
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Flexible attribute mapping
78
+ test_files: []
79
+