ananke 0.0.2 → 0.0.3

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/README.rdoc CHANGED
@@ -49,26 +49,103 @@ The Default Repository can be changed:
49
49
  ananke.default_repository = 'MyRepository'
50
50
 
51
51
  == HyperMedia
52
- Current HyperMedia support is very limited. The following example demostrates it's use:
52
+ === Linked
53
53
 
54
54
  rest :user do
55
55
  id :id
56
56
  linked :computer
57
57
  end
58
58
 
59
- To support the above example, the Respository for User needs to have a method that returns an
60
- array of id's for use in the HyperMedia links. The method signature is as follows
59
+ module Repository
60
+ module User
61
+ def self.one(id)
62
+ [Return User for User Id]
63
+ end
64
+ def self.computer_id_list(id)
65
+ [Return an array of single value id's]
66
+ end
67
+ end
68
+ end
69
+
70
+ Repository Method:
71
+
72
+ [ResourceRepository].[linked name]_id_list
61
73
 
62
- def self.computer_id_list
63
- [Return an array of single value id's]
74
+ Routes Available/Registered:
75
+
76
+ /user/:id
77
+
78
+ Output:
79
+
80
+ {
81
+ user: {
82
+ user_id: "1"
83
+ name: "One"
84
+ }
85
+ links: [
86
+ {
87
+ rel: "self"
88
+ uri: "/user/1"
89
+ },
90
+ {
91
+ rel: "computer"
92
+ uri: "/computer/2"
93
+ }
94
+ ]
95
+ }
96
+
97
+ The Respository for User needs to have a method that returns an array of id's for use in the HyperMedia links.
98
+ The link to <b>Self</b> uses this method. The output will be something like this:
99
+
100
+ === Link To and Route For
101
+
102
+ rest :user do
103
+ id :id
104
+ link_to :computer
105
+ end
106
+ rest :computer do
107
+ id :id
108
+ route_for :user
109
+ end
110
+
111
+ module Repository
112
+ module User
113
+ def self.one(id)
114
+ [Return User for User Id]
115
+ end
116
+ end
117
+ module Computer
118
+ def self.user(id)
119
+ [Return List of Computers for User Id]
120
+ end
121
+ end
64
122
  end
65
123
 
66
- It get called:
124
+ Routes Available/Registered:
125
+
126
+ /user/:id
127
+ /computer/user/:id
128
+
129
+ Output:
67
130
 
68
- [ResourceRepository].[link name]_id_list
131
+ {
132
+ user: {
133
+ user_id: "1"
134
+ name: "One"
135
+ }
136
+ links: [
137
+ {
138
+ rel: "self"
139
+ uri: "/user/1"
140
+ },
141
+ {
142
+ rel: "computer"
143
+ uri: "/computer/user/1"
144
+ }
145
+ ]
146
+ }
69
147
 
70
- We are working on adding lots more HyperMedia Support, especially to get around problems that
71
- the current HyperMedia is giving. Stay tuned...
148
+ This way of linking solves a lot of problems, and can also be used for searching support.
72
149
 
73
150
  == Media Type
74
151
  The REST media type can be built up:
@@ -128,7 +205,6 @@ A short list of future development:
128
205
  - Refactor!
129
206
  - Return Value Strategy
130
207
  - Resource Exposes Media Type
131
- - HyperMedia
132
208
  - Lots more `bullet-proofing`
133
209
  - ETag Support
134
210
 
data/Rakefile CHANGED
@@ -32,14 +32,14 @@ gemspec = Gem::Specification.new do |gem|
32
32
 
33
33
  **************************************************
34
34
  }
35
- gem.add_dependency "sinatra", "~> 1.1.2"
35
+ gem.add_dependency "sinatra", "~> 1.1.2"
36
+ gem.add_dependency "colored", "~> 1.2"
37
+ gem.add_dependency "json", "~> 1.5.1"
36
38
 
37
- gem.add_development_dependency "colored", "~> 1.2"
38
- gem.add_development_dependency "json", "~> 1.5.1"
39
- gem.add_development_dependency "rack-test", "~> 0.5.6"
40
- gem.add_development_dependency "rake", "~> 0.8.7"
41
- gem.add_development_dependency "rspec", "~> 2.5.0"
42
- gem.add_development_dependency "simplecov", "~> 0.3.9"
39
+ gem.add_development_dependency "rack-test", "~> 0.5.6"
40
+ gem.add_development_dependency "rake", "~> 0.8.7"
41
+ gem.add_development_dependency "rspec", "~> 2.5.0"
42
+ gem.add_development_dependency "simplecov", "~> 0.3.9"
43
43
  end
44
44
 
45
45
  Rake::GemPackageTask.new(gemspec) do |pkg|
data/lib/ananke.rb CHANGED
@@ -44,7 +44,7 @@ module Ananke
44
44
  mod
45
45
  end
46
46
 
47
- def get_json(path, obj)
47
+ def get_json(path, obj, links)
48
48
  if obj.nil?
49
49
  out :error, "#{path} - No return object"
50
50
  ''
@@ -52,10 +52,17 @@ module Ananke
52
52
  out :error, "#{path} - Return object cannot be converted to JSON"
53
53
  ''
54
54
  else
55
- obj.to_json
55
+ root_path = path.to_s.split('/')[0]
56
+ dic = {root_path.to_sym => obj}
57
+ dic[:links] = links unless links.nil?
58
+ dic.to_json
56
59
  end
57
60
  end
58
61
 
62
+ def get_id(obj, key)
63
+ obj.respond_to?(key) ? obj.instance_variable_get(key) : obj.class == Hash && obj.has_key?(key) ? obj[key] : nil
64
+ end
65
+
59
66
  #===========================BUILDUP============================
60
67
  def build_route(mod, mod_method, verb, route, &block)
61
68
  if mod.respond_to? mod_method
@@ -75,15 +82,17 @@ module Ananke
75
82
  end
76
83
  key = @id[:key]
77
84
  fields = @fields
78
- linkups = @linkups
85
+ link_list = @link_list
86
+ link_to_list = @link_to_list
87
+ route_for_list = @route_for_list
79
88
 
80
89
  #===========================GET/ID=============================
81
90
  build_route mod, :one, :get, "/#{path}/:#{key}" do
82
91
  param_missing!(key) if params[key].nil?
83
92
  obj = mod.one(params[key])
84
93
 
85
- json = get_json(path, obj)
86
- inject_links!(json, linkups, path, params[key], mod)
94
+ links = build_links(link_list, link_to_list, path, params[key], mod)
95
+ json = get_json(path, obj, links)
87
96
 
88
97
  status 200
89
98
  json
@@ -94,18 +103,24 @@ module Ananke
94
103
  obj_list = mod.all
95
104
 
96
105
  status 200
97
- json_list = []
106
+ #json_list = []
107
+ result_list = []
98
108
  obj_list.each do |obj|
99
- id = obj.respond_to?(key) ? obj.instance_variable_get(key) : obj.class == Hash && obj.has_key?(key) ? obj[key] : nil
109
+ id = get_id(obj, key)
100
110
  if !id.nil?
101
- json = get_json(path, obj)
102
- inject_links!(json, linkups, path, id, mod)
103
- json_list << json
111
+ dic = {path.to_sym => obj}
112
+ links = build_links(link_list, link_to_list, path, id, mod) if Ananke.settings[:links]
113
+ dic[:links] = links unless links.nil?
114
+ result_list << dic
104
115
  else
105
116
  out :error, "#{path} - Cannot find key(#{key}) on object #{obj}"
106
117
  end
107
118
  end
108
- "[#{json_list.join(',')}]"
119
+ dic = {"#{path}_list".to_sym => result_list}
120
+ link_self = build_link_self(path, '') if Ananke.settings[:links]
121
+ dic[:links] = link_self unless link_self.nil?
122
+
123
+ dic.to_json
109
124
  end
110
125
 
111
126
  #===========================POST===============================
@@ -114,8 +129,8 @@ module Ananke
114
129
  error status, message unless status.nil?
115
130
  obj = mod.add(params)
116
131
 
117
- json = get_json(path, obj)
118
- inject_links!(json, linkups, path, params[key], mod)
132
+ links = build_links(link_list, link_to_list, path, params[key], mod)
133
+ json = get_json(path, obj, links)
119
134
 
120
135
  status 201
121
136
  json
@@ -128,8 +143,8 @@ module Ananke
128
143
  error status, message unless status.nil?
129
144
  obj = mod.edit(params[key], params)
130
145
 
131
- json = get_json(path, obj)
132
- inject_links!(json, linkups, path, params[key], mod)
146
+ links = build_links(link_list, link_to_list, path, params[key], mod)
147
+ json = get_json(path, obj, links)
133
148
 
134
149
  status 200
135
150
  json
@@ -149,6 +164,20 @@ module Ananke
149
164
  build_route mod, :delete, :delete, "/#{path}/?" do
150
165
  param_missing!(key)
151
166
  end
167
+
168
+ #===========================ROUTE_FOR==========================
169
+ route_for_list.each do |r|
170
+ build_route mod, r[:name], :get, "/#{path}/#{r[:name]}/:key" do
171
+ param_missing!(:key) if params[:key].nil?
172
+ obj = mod.send(r[:name], params[:key])
173
+
174
+ links = build_links(link_list, link_to_list, "#{path}/#{r[:name]}", params[:key], mod)
175
+ json = get_json("#{path}/#{r[:name]}", obj, links)
176
+
177
+ status 200
178
+ json
179
+ end
180
+ end
152
181
  end
153
182
 
154
183
  #===========================Validation=========================
@@ -169,34 +198,51 @@ module Ananke
169
198
  error 400, "Missing Parameter: #{key.to_s}"
170
199
  end
171
200
 
172
- #===========================LINKING============================
173
- def build_links(path, id, mod, linkups)
174
- ret = [{:rel => 'self', :uri => "/#{path}/#{id}"}]
175
- linkups.each do |l|
201
+ #===========================LINKS==============================
202
+ def build_links(link_list, link_to_list, path, id, mod)
203
+ return if !Ananke.settings[:links]
204
+
205
+ links = build_link_self(path, id)
206
+ links += build_link_list(path, id, mod, link_list)
207
+ links += build_link_to_list(path, id, link_to_list)
208
+
209
+ links
210
+ end
211
+ #===========================SELF===============================
212
+ def build_link_self(path, id)
213
+ [{:rel => 'self', :uri => "/#{path}/#{id}"}]
214
+ end
215
+ #===========================LINKED=============================
216
+ def build_link_list(path, id, mod, link_list)
217
+ links = []
218
+ link_list.each do |l|
176
219
  mod_method = "#{l[:rel]}_id_list"
177
220
  if mod.respond_to?(mod_method)
178
221
  id_list = mod.send(mod_method, id)
179
- id_list.each{|i| ret << {:rel => "#{l[:rel]}", :uri => "/#{l[:rel]}/#{i}"}}
222
+ id_list.each{|i| links << {:rel => "#{l[:rel]}", :uri => "/#{l[:rel]}/#{i}"}}
180
223
  else
181
224
  out :error, "#{path} - #{mod} does not respond to '#{mod_method.to_s}'"
182
225
  end
183
226
  end
184
- ret
227
+ links
185
228
  end
186
-
187
- def inject_links!(json, linkups, path, id, mod)
188
- return if !Ananke.settings[:links]
189
- links = build_links(path, id, mod, linkups)
190
- json.insert(json.length-1, ",\"links\":#{links.to_json}")
229
+ #===========================LINK_TO============================
230
+ def build_link_to_list(path, id, link_to_list)
231
+ links = []
232
+ link_to_list.each do |l|
233
+ links << {:rel => "#{l[:rel]}", :uri => "/#{l[:rel]}/#{path}/#{id}"}
234
+ end
235
+ links
191
236
  end
192
237
 
193
238
  public
194
-
195
239
  #===========================DSL================================
196
240
  def rest(path, &block)
197
241
  @id = {}
198
242
  @fields = []
199
- @linkups = []
243
+ @link_list = []
244
+ @link_to_list = []
245
+ @route_for_list = []
200
246
  yield block
201
247
  build path
202
248
  end
@@ -210,8 +256,14 @@ module Ananke
210
256
  def optional(key, *rules)
211
257
  @fields << {:key => key, :type => :optional, :rules => rules}
212
258
  end
213
- def linkup(rel)
214
- @linkups << {:rel => rel}
259
+ def linked(rel)
260
+ @link_list << {:rel => rel}
261
+ end
262
+ def link_to(rel)
263
+ @link_to_list << {:rel => rel}
264
+ end
265
+ def route_for(rel)
266
+ @route_for_list << {:name => rel}
215
267
  end
216
268
  def rule(name, &block)
217
269
  Ananke::Rules.send(:define_singleton_method, "validate_#{name}", block)
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ananke
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -0,0 +1,31 @@
1
+ require './spec/spec_helper'
2
+ require './lib/ananke'
3
+
4
+ describe 'Link-To Resource' do
5
+ include Rack::Test::Methods
6
+ include Ananke
7
+
8
+ def app
9
+ Sinatra::Base
10
+ end
11
+
12
+ it """
13
+ Should be able to Output link to link_to
14
+ """ do
15
+ module Repository
16
+ module Link_to
17
+ def self.one(id)
18
+ {:link_id => id}
19
+ end
20
+ end
21
+ end
22
+ rest :link_to do
23
+ id :link_id
24
+ link_to :to
25
+ end
26
+
27
+ get "/link_to/1"
28
+ check_status(200)
29
+ last_response.body.should == '{"link_to":{"link_id":"1"},"links":[{"rel":"self","uri":"/link_to/1"},{"rel":"to","uri":"/to/link_to/1"}]}'
30
+ end
31
+ end
@@ -26,7 +26,7 @@ describe 'Resource' do
26
26
 
27
27
  post "/self", body={:user_id => 1, :username => '1234'}
28
28
  check_status(201)
29
- last_response.body.should == '{"user_id":1,"links":[{"rel":"self","uri":"/self/1"}]}'
29
+ last_response.body.should == '{"self":{"user_id":1},"links":[{"rel":"self","uri":"/self/1"}]}'
30
30
 
31
31
  hash = JSON.parse(last_response.body)
32
32
  uri = hash['links'].map{|l| l['uri'] if l['rel'] == 'self'}[0]
@@ -35,7 +35,7 @@ describe 'Resource' do
35
35
  end
36
36
 
37
37
  module Repository
38
- module Linkup
38
+ module Linked
39
39
  def self.one(id)
40
40
  {:user_id => 1}
41
41
  end
@@ -56,20 +56,20 @@ describe 'Resource' do
56
56
  def self.one(id) end
57
57
  end
58
58
  end
59
- rest :linkup do
59
+ rest :linked do
60
60
  id :user_id
61
- linkup :line
61
+ linked :line
62
62
  end
63
63
  rest :line do
64
64
  id :line_id
65
65
  end
66
66
 
67
67
  it """
68
- Should be able to Output links to linkups and Call those links
68
+ Should be able to Output links to linkeds and Call those links
69
69
  """ do
70
- post "/linkup", body={:user_id => 1, :username => '1234'}
70
+ post "/linked", body={:user_id => 1, :username => '1234'}
71
71
  check_status(201)
72
- last_response.body.should == '{"user_id":1,"links":[{"rel":"self","uri":"/linkup/1"},{"rel":"line","uri":"/line/1"},{"rel":"line","uri":"/line/2"}]}'
72
+ last_response.body.should == '{"linked":{"user_id":1},"links":[{"rel":"self","uri":"/linked/1"},{"rel":"line","uri":"/line/1"},{"rel":"line","uri":"/line/2"}]}'
73
73
 
74
74
  hash = JSON.parse(last_response.body)
75
75
  hash['links'].each do |l|
@@ -79,29 +79,29 @@ describe 'Resource' do
79
79
  end
80
80
 
81
81
  it "Should return links on Get One" do
82
- #get "/linkup/1"
83
- #check_status(200)
84
- #last_response.body.should == '{"user_id":1,"links":[{"rel":"self","uri":"/linkup/1"},{"rel":"line","uri":"/line/1"},{"rel":"line","uri":"/line/2"}]}'
82
+ get "/linked/1"
83
+ check_status(200)
84
+ last_response.body.should == '{"linked":{"user_id":1},"links":[{"rel":"self","uri":"/linked/1"},{"rel":"line","uri":"/line/1"},{"rel":"line","uri":"/line/2"}]}'
85
85
  end
86
86
 
87
87
  it """
88
88
  Should not inject links where it cannot find Repository Id lookup method
89
89
  """ do
90
90
  module Repository
91
- module Linkup_fail
91
+ module Linked_fail
92
92
  def self.one(id) end
93
93
  def self.add(data)
94
94
  {:user_id => 1}
95
95
  end
96
96
  end
97
97
  end
98
- rest :linkup_fail do
98
+ rest :linked_fail do
99
99
  id :user_id
100
- linkup :line
100
+ linked :line
101
101
  end
102
102
 
103
- post "/linkup_fail", body={:user_id => 1, :username => '1234'}
103
+ post "/linked_fail", body={:user_id => 1, :username => '1234'}
104
104
  check_status(201)
105
- last_response.body.should == '{"user_id":1,"links":[{"rel":"self","uri":"/linkup_fail/1"}]}'
105
+ last_response.body.should == '{"linked_fail":{"user_id":1},"links":[{"rel":"self","uri":"/linked_fail/1"}]}'
106
106
  end
107
107
  end
@@ -0,0 +1,31 @@
1
+ require './spec/spec_helper'
2
+ require './lib/ananke'
3
+
4
+ describe 'Resource Route-For' do
5
+ include Rack::Test::Methods
6
+ include Ananke
7
+
8
+ def app
9
+ Sinatra::Base
10
+ end
11
+
12
+ it """
13
+ Should be able to accept route_for Registrations
14
+ """ do
15
+ module Repository
16
+ module Route_for
17
+ def self.custom(id)
18
+ {:content => 'Test'}
19
+ end
20
+ end
21
+ end
22
+ rest :route_for do
23
+ id :link_id
24
+ route_for :custom
25
+ end
26
+
27
+ get "/route_for/custom/1"
28
+ check_status(200)
29
+ last_response.body.should == '{"route_for":{"content":"Test"},"links":[{"rel":"self","uri":"/route_for/custom/1"}]}'
30
+ end
31
+ end
@@ -60,41 +60,41 @@ describe 'Basic Ananke REST' do
60
60
  """ do
61
61
  get "/user"
62
62
  check_status(200)
63
- last_response.body.should == Repository::User.data.to_json
63
+ last_response.body.should == '{"user_list":[{"user":{"user_id":1,"username":"one"}},{"user":{"user_id":2,"username":"two"}}]}'
64
64
  end
65
65
 
66
66
  it """
67
67
  GET /user/1
68
68
  - code: 200
69
69
  - content-type: text/plain
70
- - body: {user_id: ,username: ,email: ,country: }
70
+ - body: {user_id: ,username: }
71
71
  """ do
72
72
  get "/user/1"
73
73
  check_status(200)
74
- last_response.body.should == Repository::User.data[0].to_json
74
+ last_response.body.should == '{"user":{"user_id":1,"username":"one"}}'
75
75
  end
76
76
 
77
77
  it """
78
78
  POST /user
79
- - body: {user_id: ,username: ,email: ,country: }
79
+ - body: {user_id: ,username: }
80
80
  RETURN
81
81
  - code: 201
82
82
  - content-type: text/json
83
83
  - body:
84
84
  """ do
85
- post "/user", body={:user_id => 3, :username => 'three', :email => '3@three.com', :country => 'USA'}
85
+ post "/user", body={:user_id => 3, :username => 'three'}
86
86
  check_status(201)
87
87
  end
88
88
 
89
89
  it """
90
90
  PUT /user/3
91
- - body: {user_id: ,username: ,email: ,country: }
91
+ - body: {user_id: ,username: }
92
92
  RETURN
93
93
  - code: 200
94
94
  - content-type: text/json
95
95
  - body:
96
96
  """ do
97
- put "/user/3", body={:user_id => 3, :username => 'four', :email => '4@four.com', :country => 'Russia'}
97
+ put "/user/3", body={:user_id => 3, :username => 'four'}
98
98
  check_status(200)
99
99
  end
100
100
 
@@ -112,13 +112,13 @@ describe 'Basic Ananke REST' do
112
112
  #----------------------------FAILS--------------------------------------
113
113
  it """
114
114
  PUT /user
115
- - body: {user_id: ,username: ,email: ,country: }
115
+ - body: {user_id: ,username: }
116
116
  RETURN
117
117
  - code: 400
118
118
  - content-type: text/json
119
119
  - body: Missing Parameter: user_id
120
120
  """ do
121
- put "/user", body={:user_id => 3, :username => 'four', :email => '4@four.com', :country => 'Russia'}
121
+ put "/user", body={:user_id => 3, :username => 'four'}
122
122
  check_status(400)
123
123
  last_response.body.should == 'Missing Parameter: user_id'
124
124
  end
@@ -138,8 +138,8 @@ end
138
138
 
139
139
  module Repository
140
140
  module User
141
- @data = [{:user_id => 1, :username => 'one', :email => '1@one.com', :country => 'South Africa'},
142
- {:user_id => 2, :username => 'two', :email => '2@two.com', :country => 'England'}]
141
+ @data = [{:user_id => 1, :username => 'one'},
142
+ {:user_id => 2, :username => 'two'}]
143
143
 
144
144
  def self.data
145
145
  @data
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: ananke
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.2
5
+ version: 0.0.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Andries Coetzee
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-02-10 00:00:00 +02:00
13
+ date: 2011-02-11 00:00:00 +02:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -33,7 +33,7 @@ dependencies:
33
33
  - - ~>
34
34
  - !ruby/object:Gem::Version
35
35
  version: "1.2"
36
- type: :development
36
+ type: :runtime
37
37
  version_requirements: *id002
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: json
@@ -44,7 +44,7 @@ dependencies:
44
44
  - - ~>
45
45
  - !ruby/object:Gem::Version
46
46
  version: 1.5.1
47
- type: :development
47
+ type: :runtime
48
48
  version_requirements: *id003
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: rack-test
@@ -104,9 +104,11 @@ files:
104
104
  - spec/dumping.rb
105
105
  - spec/cov_adapter.rb
106
106
  - spec/lib/ananke_spec.rb
107
+ - spec/lib/ananke_linked_spec.rb
107
108
  - spec/lib/ananke_out_spec.rb
108
- - spec/lib/ananke_links_spec.rb
109
109
  - spec/lib/validation_spec.rb
110
+ - spec/lib/ananke_link_to_spec.rb
111
+ - spec/lib/ananke_route_for_spec.rb
110
112
  - spec/spec_helper.rb
111
113
  - spec/call_chain.rb
112
114
  - spec/nice_formatter.rb
@@ -120,7 +122,7 @@ licenses: []
120
122
  post_install_message: |
121
123
  **************************************************
122
124
 
123
- Thank you for installing ananke-0.0.2
125
+ Thank you for installing ananke-0.0.3
124
126
 
125
127
  Please be sure to look at README.rdoc to see what might have changed
126
128
  since the last release and how to use this GEM.
@@ -149,14 +151,16 @@ rubyforge_project:
149
151
  rubygems_version: 1.5.0
150
152
  signing_key:
151
153
  specification_version: 3
152
- summary: ananke-0.0.2
154
+ summary: ananke-0.0.3
153
155
  test_files:
154
156
  - spec/dumping.rb
155
157
  - spec/cov_adapter.rb
156
158
  - spec/lib/ananke_spec.rb
159
+ - spec/lib/ananke_linked_spec.rb
157
160
  - spec/lib/ananke_out_spec.rb
158
- - spec/lib/ananke_links_spec.rb
159
161
  - spec/lib/validation_spec.rb
162
+ - spec/lib/ananke_link_to_spec.rb
163
+ - spec/lib/ananke_route_for_spec.rb
160
164
  - spec/spec_helper.rb
161
165
  - spec/call_chain.rb
162
166
  - spec/nice_formatter.rb