elastic_attributes 0.1.0

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/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
+