jeokkarak 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +211 -0
- data/Rakefile +54 -0
- data/lib/hashi.rb +41 -0
- data/lib/jeokkarak.rb +64 -0
- metadata +58 -0
data/README.textile
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
h1. Schema evolution
|
2
|
+
|
3
|
+
One of the main issues with service oriented architectures is the use of strong coupled schema definitions shared between clients and servers.
|
4
|
+
|
5
|
+
In order to provide forward-compatibility, clients should be able to accept formats that contain
|
6
|
+
new content, without breaking own their own.
|
7
|
+
|
8
|
+
Jeokkarak and Hashi makes the perfect couple for this task: given a hash (parsed from a xml file), Jeokkarak will either:
|
9
|
+
|
10
|
+
* instantiate your rail's ActiveRecord type and use its relations
|
11
|
+
* instantiate your type and use pre-defined relations
|
12
|
+
* create a pseudo-object backed on the hash
|
13
|
+
|
14
|
+
In any of those cases, Jeokkarak will let your server evolve their schema, without requiring you to add new fields to your type.
|
15
|
+
|
16
|
+
h1. Jeokkarak
|
17
|
+
|
18
|
+
Jeokkarak is the korean version of hashis. In our case, Jeokkarak is used to support
|
19
|
+
evolution-ready schemas and loosely coupled systems in the web.
|
20
|
+
By creating your own class only with the attributes you require:
|
21
|
+
|
22
|
+
<pre>
|
23
|
+
class Player
|
24
|
+
acts_as_jeokkarak
|
25
|
+
attr_accessor :name
|
26
|
+
end
|
27
|
+
</pre>
|
28
|
+
|
29
|
+
You can still parse the entire hash and set the fields that you know, and allow schema evolution
|
30
|
+
(and forward compatibility) so that the new fields are dynamically created in your object:
|
31
|
+
|
32
|
+
<pre>
|
33
|
+
# using the existing field
|
34
|
+
hash = {"name" => "guilherme silveira"}
|
35
|
+
player = Player.from_hash(hash)
|
36
|
+
player.name.should == ("guilherme silveira")
|
37
|
+
|
38
|
+
# dinamically creating a field
|
39
|
+
hash = {"age" => 29}
|
40
|
+
player = Player.from_hash(hash)
|
41
|
+
player.age.should == 29
|
42
|
+
</pre>
|
43
|
+
|
44
|
+
h1. Hashi
|
45
|
+
|
46
|
+
It's a common task to simulate objects from hashes, but one has to remember using hash access format in order to access it. Hashi helps you by allowing direct access to its content:
|
47
|
+
|
48
|
+
<pre>
|
49
|
+
hash = { :team => {:players => [{:name=>"guilherme silveira"},{:name=>"jose donizetti"}]}}
|
50
|
+
object = Hashi.to_object(hash)
|
51
|
+
puts object.team.players[0].name
|
52
|
+
</pre>
|
53
|
+
|
54
|
+
|
55
|
+
h1. What is Hashi?
|
56
|
+
|
57
|
+
h2. The reality without Hashi
|
58
|
+
|
59
|
+
<pre>
|
60
|
+
hash = { :team => {:players => [{:name=>"guilherme silveira"},{:name=>"jose donizetti"}]}}
|
61
|
+
puts hash[:team][:players][0][:name] # his name
|
62
|
+
</pre>
|
63
|
+
|
64
|
+
h2. The reality without Hashi, within Rails
|
65
|
+
|
66
|
+
Although the Ruby language provides an easy way to access hashes, it becomes easier if you use Rails with "Matt Pulver's":http://www.xcombinator.com/2008/07/06/activerecord-from_json-and-from_xml/ code:
|
67
|
+
|
68
|
+
<pre>
|
69
|
+
class Team < ActiveRecord::Base
|
70
|
+
has_many :players
|
71
|
+
end
|
72
|
+
class Player < ActiveRecord::Base
|
73
|
+
# create a migration with field name:string
|
74
|
+
end
|
75
|
+
hash = { :team => {:players => [{:name=>"guilherme silveira"},{:name=>"jose donizetti"}]}}
|
76
|
+
puts Team.from_xml(hash).players[0].name
|
77
|
+
</pre>
|
78
|
+
|
79
|
+
h2. How does it compare to Hashie?
|
80
|
+
|
81
|
+
Hashi actually does not create your attributes, instead it deals with *method_missing* invocations to
|
82
|
+
simulate those properties, never creating a copy of your hash.
|
83
|
+
|
84
|
+
h2. Updated hash
|
85
|
+
|
86
|
+
You can access the original hash (updated if you have made any changes) by one of those two ways:
|
87
|
+
|
88
|
+
<pre>
|
89
|
+
hash = { :team => {:players => [{:name=>"guilherme silveira"},{:name=>"jose donizetti"}]}}
|
90
|
+
object = Hashi.to_object(hash)
|
91
|
+
object.team.players[0].name = 'jose donizetti'
|
92
|
+
|
93
|
+
# the original hash was modified!
|
94
|
+
puts hash
|
95
|
+
|
96
|
+
# you can extract the original hash if you wish
|
97
|
+
puts object.hash
|
98
|
+
|
99
|
+
</pre>
|
100
|
+
|
101
|
+
h1. Jeokkarak
|
102
|
+
|
103
|
+
h2. Relationships
|
104
|
+
|
105
|
+
Jeokkarak supports schema evoluted relationships: newly created relationships will be treated as Hashi's:
|
106
|
+
|
107
|
+
<pre>
|
108
|
+
hash = {"player" => [{"name" => "guilherme silveira"}, {"name" => "caue guerra"}], "new_relation" => {"new_entry" => 20}}
|
109
|
+
team = Team.from_hash(hash)
|
110
|
+
team.new_relation.new_entry.should eql(20)
|
111
|
+
</pre>
|
112
|
+
|
113
|
+
It also supports pre-existing relations through the use of has_child:
|
114
|
+
|
115
|
+
<pre>
|
116
|
+
class Team
|
117
|
+
acts_as_jeokkarak
|
118
|
+
has_child Player, :as => "player"
|
119
|
+
end
|
120
|
+
|
121
|
+
# will instantiate a Player type:
|
122
|
+
hash = {"player" => {"name" => "guilherme silveira"}}
|
123
|
+
team = Team.from_hash(hash)
|
124
|
+
team.player.class.should eql(Player)
|
125
|
+
</pre>
|
126
|
+
|
127
|
+
And finally, it supports rails's ActiveRecord:
|
128
|
+
|
129
|
+
<pre>
|
130
|
+
class Team < ActiveRecord::Base
|
131
|
+
has_many :players
|
132
|
+
end
|
133
|
+
|
134
|
+
hash = {"players" => [{"name" => "guilherme silveira"}]}
|
135
|
+
team = Team.from_hash(hash)
|
136
|
+
team.players[0].class.should eql(Player)
|
137
|
+
</pre>
|
138
|
+
|
139
|
+
h1. Installing
|
140
|
+
|
141
|
+
h2. Ruby
|
142
|
+
|
143
|
+
gem install gemcutter
|
144
|
+
gem tumble
|
145
|
+
gem install jeokkarak
|
146
|
+
|
147
|
+
h2. Rails
|
148
|
+
|
149
|
+
Just add in your environment.rb the following line:
|
150
|
+
|
151
|
+
<pre>
|
152
|
+
config.gem "jeokkarak", :source => "http://gemcutter.org"
|
153
|
+
</pre>
|
154
|
+
|
155
|
+
And then execute:
|
156
|
+
<pre>rake gems:install</pre>
|
157
|
+
|
158
|
+
or, if you prefer to install it as a plugin:
|
159
|
+
|
160
|
+
<pre>script/plugin install git://github.com/caelum/jeokkarak.git</pre>
|
161
|
+
|
162
|
+
|
163
|
+
h2. Help
|
164
|
+
|
165
|
+
If you are looking for or want to help, let us know at the mailing list:
|
166
|
+
|
167
|
+
"http://groups.google.com/group/jeokkarak":http://groups.google.com/group/jeokkarak
|
168
|
+
|
169
|
+
h2. Team
|
170
|
+
|
171
|
+
Hashi was created and is maintained within Caelum:http://www.caelum.com.br by
|
172
|
+
|
173
|
+
Projetct Founder
|
174
|
+
* "Guilherme Silveira":mailto:guilherme.silveira@caelum.com.br - twitter:http://www.twitter.com/guilhermecaelum "http://guilhermesilveira.wordpress.com":http://guilhermesilveira.wordpress.com
|
175
|
+
|
176
|
+
Active Commiters
|
177
|
+
* Guilherme Silveira
|
178
|
+
* Jose Donizetti
|
179
|
+
|
180
|
+
h3. Sources
|
181
|
+
|
182
|
+
You can see its source code at: "github":http://github.com/caelum/jeokkarak
|
183
|
+
|
184
|
+
h2. What's new
|
185
|
+
|
186
|
+
h3. 1.0.1
|
187
|
+
|
188
|
+
* Supports active record relationships
|
189
|
+
* Supports jeokkarak relationships
|
190
|
+
* Supports boolean invocations with ?
|
191
|
+
* Non-existing fields will throw an exception
|
192
|
+
|
193
|
+
h2. License
|
194
|
+
|
195
|
+
/***
|
196
|
+
* Copyright (c) 2009 Caelum - www.caelumobjects.com.br
|
197
|
+
* All rights reserved.
|
198
|
+
*
|
199
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
200
|
+
* you may not use this file except in compliance with the License.
|
201
|
+
* You may obtain a copy of the License at
|
202
|
+
*
|
203
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
204
|
+
*
|
205
|
+
* Unless required by applicable law or agreed to in writing, software
|
206
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
207
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
208
|
+
* See the License for the specific language governing permissions and
|
209
|
+
* limitations under the License.
|
210
|
+
*/
|
211
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/specification'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
GEM = "jeokkarak"
|
8
|
+
GEM_VERSION = "1.0.1"
|
9
|
+
SUMMARY = "Hash to object helper methods for schema evolution forward-compatible services."
|
10
|
+
AUTHOR = "Guilherme Silveira, Jose Donizetti"
|
11
|
+
EMAIL = "guilherme.silveira@caelum.com.br"
|
12
|
+
HOMEPAGE = "http://github.com/caelum/jeokkarak"
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.name = GEM
|
16
|
+
s.version = GEM_VERSION
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.summary = SUMMARY
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
s.files = FileList['lib/**/*.rb', '[A-Z]*'].to_a
|
21
|
+
|
22
|
+
# s.add_dependency(%q<rubigen>, [">= 1.3.4"])
|
23
|
+
|
24
|
+
s.author = AUTHOR
|
25
|
+
s.email = EMAIL
|
26
|
+
s.homepage = HOMEPAGE
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new do |t|
|
30
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
31
|
+
t.spec_opts = %w(-fs -fh:doc/specs.html --color)
|
32
|
+
end
|
33
|
+
|
34
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
35
|
+
pkg.gem_spec = spec
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Install the gem locally"
|
39
|
+
task :install => [:package] do
|
40
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Create a gemspec file"
|
44
|
+
task :make_spec do
|
45
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
46
|
+
file.puts spec.to_ruby
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Builds the project"
|
51
|
+
task :build => :spec
|
52
|
+
|
53
|
+
desc "Default build will run specs"
|
54
|
+
task :default => :spec
|
data/lib/hashi.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Hashi
|
2
|
+
class UndefinedMethod
|
3
|
+
end
|
4
|
+
class CustomHash
|
5
|
+
|
6
|
+
attr_reader :hash
|
7
|
+
|
8
|
+
def initialize(h)
|
9
|
+
@hash = h
|
10
|
+
end
|
11
|
+
def method_missing(name, *args)
|
12
|
+
name = name.to_s if name.kind_of? Symbol
|
13
|
+
if name[-1,1] == "?"
|
14
|
+
parse(@hash[name.chop])
|
15
|
+
elsif name[-1,1] == "="
|
16
|
+
@hash[name.chop] = args[0]
|
17
|
+
else
|
18
|
+
parse(transform(@hash[name]))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
def [](x)
|
22
|
+
transform(@hash[x])
|
23
|
+
end
|
24
|
+
private
|
25
|
+
def transform(value)
|
26
|
+
return CustomHash.new(value) if value.kind_of?(Hash) || value.kind_of?(Array)
|
27
|
+
value
|
28
|
+
end
|
29
|
+
def parse(val)
|
30
|
+
raise Hashi::UndefinedMethod.new if val.nil?
|
31
|
+
val
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
def self.from_hash(h)
|
36
|
+
CustomHash.new(h)
|
37
|
+
end
|
38
|
+
def self.to_object(h)
|
39
|
+
CustomHash.new(h)
|
40
|
+
end
|
41
|
+
end
|
data/lib/jeokkarak.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'hashi'
|
2
|
+
|
3
|
+
module Jeokkarak
|
4
|
+
module Base
|
5
|
+
|
6
|
+
# defines that this type has a child element
|
7
|
+
def has_child(type, options={})
|
8
|
+
resource_children[options[:as]] = type
|
9
|
+
end
|
10
|
+
|
11
|
+
# checks what is the type element for this type (supports rails ActiveRecord, has_child and Hashi)
|
12
|
+
def child_type_for(name)
|
13
|
+
return reflect_on_association(key.to_sym ).klass if respond_to? :reflect_on_association
|
14
|
+
resource_children[name] || Hashi
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns the registered children list for this resource
|
18
|
+
def resource_children
|
19
|
+
@children ||= {}
|
20
|
+
@children
|
21
|
+
end
|
22
|
+
|
23
|
+
# creates an instance of this type based on this hash
|
24
|
+
def from_hash(h)
|
25
|
+
h = h.dup
|
26
|
+
result = self.new
|
27
|
+
result._internal_hash = h
|
28
|
+
h.each do |key,value|
|
29
|
+
from_hash_parse result, h, key, value
|
30
|
+
end
|
31
|
+
def result.method_missing(name, *args, &block)
|
32
|
+
Hashi.to_object(@_internal_hash).send(name, args[0], block)
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
# extension point to parse a value
|
38
|
+
def from_hash_parse(result,h,key,value)
|
39
|
+
case value.class.to_s
|
40
|
+
when 'Array'
|
41
|
+
h[key].map! { |e| child_type_for(key).from_hash e }
|
42
|
+
when /\AHash(WithIndifferentAccess)?\Z/
|
43
|
+
h[key] = child_type_for(key ).from_hash value
|
44
|
+
end
|
45
|
+
name = "#{key}="
|
46
|
+
result.send(name, value) if result.respond_to?(name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module Jeokkarak
|
52
|
+
module Config
|
53
|
+
|
54
|
+
# entry point to define a jeokkarak type
|
55
|
+
def acts_as_jeokkarak
|
56
|
+
self.module_eval do
|
57
|
+
attr_accessor :_internal_hash
|
58
|
+
end
|
59
|
+
self.extend Jeokkarak::Base
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Object.extend Jeokkarak::Config
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jeokkarak
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guilherme Silveira, Jose Donizetti
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-02 00:00:00 -02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: guilherme.silveira@caelum.com.br
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/hashi.rb
|
26
|
+
- lib/jeokkarak.rb
|
27
|
+
- Rakefile
|
28
|
+
- README.textile
|
29
|
+
has_rdoc: true
|
30
|
+
homepage: http://github.com/caelum/jeokkarak
|
31
|
+
licenses: []
|
32
|
+
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: "0"
|
43
|
+
version:
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
requirements: []
|
51
|
+
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.3.5
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Hash to object helper methods for schema evolution forward-compatible services.
|
57
|
+
test_files: []
|
58
|
+
|