jeokkarak 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.textile +211 -0
  2. data/Rakefile +54 -0
  3. data/lib/hashi.rb +41 -0
  4. data/lib/jeokkarak.rb +64 -0
  5. 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
+