restfulie 0.6.0 → 0.7.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/README.textile +83 -7
- data/Rakefile +98 -13
- data/lib/restfulie/client/base.rb +48 -53
- data/lib/restfulie/client/configuration.rb +69 -0
- data/lib/restfulie/client/http/adapter.rb +487 -0
- data/lib/restfulie/client/http/atom_ext.rb +87 -0
- data/lib/restfulie/client/http/cache.rb +28 -0
- data/lib/restfulie/client/http/error.rb +80 -0
- data/lib/restfulie/client/http/marshal.rb +147 -0
- data/lib/restfulie/client/http.rb +13 -0
- data/lib/restfulie/client.rb +8 -56
- data/lib/restfulie/common/builder/builder_base.rb +58 -0
- data/lib/restfulie/common/builder/helpers.rb +22 -0
- data/lib/restfulie/common/builder/marshalling/atom.rb +197 -0
- data/lib/restfulie/common/builder/marshalling/base.rb +12 -0
- data/lib/restfulie/common/builder/marshalling/json.rb +2 -0
- data/lib/restfulie/common/builder/marshalling.rb +16 -0
- data/lib/restfulie/common/builder/rules/collection_rule.rb +10 -0
- data/lib/restfulie/common/builder/rules/link.rb +20 -0
- data/lib/restfulie/common/builder/rules/links.rb +9 -0
- data/lib/restfulie/common/builder/rules/member_rule.rb +8 -0
- data/lib/restfulie/common/builder/rules/namespace.rb +25 -0
- data/lib/restfulie/common/builder/rules/rules_base.rb +76 -0
- data/lib/restfulie/common/builder.rb +16 -0
- data/lib/restfulie/common/errors.rb +9 -0
- data/lib/restfulie/{logger.rb → common/logger.rb} +3 -5
- data/lib/restfulie/common/representation/atom.rb +48 -0
- data/lib/restfulie/common/representation/generic.rb +33 -0
- data/lib/restfulie/common/representation/xml.rb +24 -0
- data/lib/restfulie/common/representation.rb +10 -0
- data/lib/restfulie/common.rb +23 -0
- data/lib/restfulie/server/action_controller/base.rb +31 -0
- data/lib/restfulie/server/action_controller/params_parser.rb +62 -0
- data/lib/restfulie/server/action_controller/restful_responder.rb +39 -0
- data/lib/restfulie/server/action_controller/routing/restful_route.rb +14 -0
- data/lib/restfulie/server/action_controller/routing.rb +12 -0
- data/lib/restfulie/server/action_controller.rb +15 -0
- data/lib/restfulie/server/action_view/helpers.rb +45 -0
- data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +15 -0
- data/lib/restfulie/server/action_view/template_handlers.rb +13 -0
- data/lib/restfulie/server/action_view.rb +8 -0
- data/lib/restfulie/server/configuration.rb +21 -0
- data/lib/restfulie/server/core_ext/array.rb +45 -0
- data/lib/restfulie/server/core_ext.rb +1 -0
- data/lib/restfulie/server/restfulie_controller.rb +1 -17
- data/lib/restfulie/server.rb +15 -0
- data/lib/restfulie.rb +4 -72
- data/lib/vendor/atom/configuration.rb +24 -0
- data/lib/vendor/atom/pub.rb +250 -0
- data/lib/vendor/atom/xml/parser.rb +373 -0
- data/lib/vendor/atom.rb +771 -0
- metadata +94 -33
- data/lib/restfulie/client/atom_media_type.rb +0 -75
- data/lib/restfulie/client/cache.rb +0 -103
- data/lib/restfulie/client/entry_point.rb +0 -94
- data/lib/restfulie/client/extensions/http.rb +0 -116
- data/lib/restfulie/client/helper.rb +0 -28
- data/lib/restfulie/client/instance.rb +0 -158
- data/lib/restfulie/client/request_execution.rb +0 -321
- data/lib/restfulie/client/state.rb +0 -36
- data/lib/restfulie/media_type.rb +0 -143
- data/lib/restfulie/media_type_control.rb +0 -115
- data/lib/restfulie/media_type_defaults.rb +0 -51
- data/lib/restfulie/server/atom_media_type.rb +0 -115
- data/lib/restfulie/server/base.rb +0 -91
- data/lib/restfulie/server/controller.rb +0 -122
- data/lib/restfulie/server/instance.rb +0 -102
- data/lib/restfulie/server/marshalling.rb +0 -47
- data/lib/restfulie/server/opensearch/description.rb +0 -54
- data/lib/restfulie/server/opensearch.rb +0 -18
- data/lib/restfulie/server/transition.rb +0 -93
- data/lib/restfulie/unmarshalling.rb +0 -131
- data/lib/vendor/jeokkarak/hashi.rb +0 -65
- data/lib/vendor/jeokkarak/jeokkarak.rb +0 -81
data/README.textile
CHANGED
@@ -1,21 +1,97 @@
|
|
1
1
|
h1. Web site
|
2
2
|
|
3
|
-
|
3
|
+
Restfulie's website can be found at "http://restfulie.caelum.com.br":http://restfulie.caelum.com.br
|
4
|
+
Mikyung's, a REST agent framework built on top of Restfulie can be seen at "http://github.com/caelum/mikyung":http://github.com/caelum/mikyung.
|
4
5
|
|
5
6
|
h1. Quit pretending
|
6
7
|
|
7
|
-
CRUD through HTTP is a good step forward to using resources and becoming RESTful, another step further into it is to make use of hypermedia
|
8
|
+
CRUD through HTTP is a good step forward to using resources and becoming RESTful, another step further into it is to make use of hypermedia and semantic meaningful media types: this gem allows you to do it really fast.
|
8
9
|
|
9
10
|
You can read the "article on using the web for real":http://guilhermesilveira.wordpress.com/2009/11/03/quit-pretending-use-the-web-for-real-restfulie/ which gives an introduction to hypermedia/resources/services.
|
10
11
|
|
11
12
|
h2. Why would I use restfulie?
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
# Easy --> writing hypermedia and semantic meaningful media type aware clients
|
15
|
+
# Small -> it's not a bloated solution with a huge list of APIs
|
16
|
+
# HATEOAS --> clients you are unaware of will not bother if you change your URIs
|
17
|
+
# HATEOAS --> resources that you consume will not affect your software whenever they change part of their flow or URIs
|
18
|
+
# Adaptability --> clients are able to adapt to your changes
|
19
|
+
|
20
|
+
h2. Installing
|
18
21
|
|
22
|
+
For client side usage, execute:
|
23
|
+
|
24
|
+
<pre>
|
25
|
+
gem install activesupport
|
26
|
+
gem install restfulie
|
27
|
+
</pre>
|
28
|
+
|
29
|
+
For server side usage, execute:
|
30
|
+
|
31
|
+
<pre>
|
32
|
+
gem install restfulie
|
33
|
+
</pre>
|
34
|
+
|
35
|
+
h2. Simple server example
|
36
|
+
|
37
|
+
In the server side, all you need to do is notify inherited_resources which media types you are able to represent your resource:
|
38
|
+
|
39
|
+
<pre>
|
40
|
+
class OrdersController < ApplicationController
|
41
|
+
include Restfulie::Server::ActionController::Base
|
42
|
+
|
43
|
+
respond_to :atom, :html, :xml, :commerce, :opensearch
|
44
|
+
|
45
|
+
def index
|
46
|
+
respond_with @orders = Order.all
|
47
|
+
end
|
48
|
+
|
49
|
+
def show
|
50
|
+
respond_with @order = Order.find(params[:id])
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
</pre>
|
55
|
+
|
56
|
+
That's it. Restfulie will take care of rendering a valid representation according to content negotiation. You can configure the rendering process through a custom tokamak view:
|
57
|
+
|
58
|
+
<pre>
|
59
|
+
describe_collection(@orders) do |collection|
|
60
|
+
collection.id = orders_url
|
61
|
+
collection.links << link( :rel => :create, :href => orders_url )
|
62
|
+
collection.describe_members
|
63
|
+
end
|
64
|
+
</pre>
|
65
|
+
|
66
|
+
You can go through an entire application by "watching this video":http://guilhermesilveira.wordpress.com or "downloading an example application":http://github.com/caelum/mikyung.
|
67
|
+
|
68
|
+
h2. Simple client example
|
69
|
+
|
70
|
+
The following example is a partial REST client implementation that navigates through some relations:
|
71
|
+
|
72
|
+
<pre>
|
73
|
+
order = Restfulie.at('http://localhost:3000/orders/1').get!
|
74
|
+
order.items.each { |item| puts items }
|
75
|
+
receipt = order.payment.post! { :amount => 500 }
|
76
|
+
puts receipt.id
|
77
|
+
</pre>
|
78
|
+
|
79
|
+
In order to create a full REST client, "watch this video":http://guilhermesilveira.wordpress.com.
|
80
|
+
|
81
|
+
h2. Download source example
|
82
|
+
|
83
|
+
You can view an entire application running Restfulie under *spec/integration/order/server* and *spec/integration/order/client* in this git repository.
|
84
|
+
|
85
|
+
"You can also download a full example of a REST based agent and server":http://github.com/caelum/mikyung using Restfulie and Mikyung, according to the Rest Architecture Maturity Model.
|
86
|
+
|
87
|
+
h2. Building the project
|
88
|
+
|
89
|
+
If you want to build the project and run its tests, remember to install all (client and server) required gems and:
|
90
|
+
|
91
|
+
<pre>
|
92
|
+
gem install rack-conneg
|
93
|
+
gem install responders_backport
|
94
|
+
</pre>
|
19
95
|
|
20
96
|
<script type="text/javascript">
|
21
97
|
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
data/Rakefile
CHANGED
@@ -3,12 +3,13 @@ require 'rubygems/specification'
|
|
3
3
|
require 'rake'
|
4
4
|
require 'rake/gempackagetask'
|
5
5
|
require 'spec/rake/spectask'
|
6
|
+
require 'rake/rdoctask'
|
6
7
|
|
7
8
|
GEM = "restfulie"
|
8
|
-
GEM_VERSION = "0.
|
9
|
-
SUMMARY
|
10
|
-
AUTHOR
|
11
|
-
EMAIL
|
9
|
+
GEM_VERSION = "0.7.0"
|
10
|
+
SUMMARY = "Hypermedia aware resource based library in ruby (client side) and ruby on rails (server side)."
|
11
|
+
AUTHOR = "Guilherme Silveira, Caue Guerra"
|
12
|
+
EMAIL = "guilherme.silveira@caelum.com.br"
|
12
13
|
HOMEPAGE = "http://restfulie.caelumobjects.com"
|
13
14
|
|
14
15
|
spec = Gem::Specification.new do |s|
|
@@ -18,28 +19,111 @@ spec = Gem::Specification.new do |s|
|
|
18
19
|
s.summary = SUMMARY
|
19
20
|
s.require_paths = ['lib']
|
20
21
|
s.files = FileList['lib/**/*.rb', '[A-Z]*'].to_a
|
21
|
-
s.add_dependency("
|
22
|
-
|
23
|
-
|
24
|
-
# s.add_dependency(%q<rubigen>, [">= 1.3.4"])
|
22
|
+
s.add_dependency("actionpack", [">= 2.3.2"])
|
23
|
+
s.add_dependency("activesupport", [">= 2.3.2"])
|
24
|
+
s.add_dependency("responders_backport", ["~> 0.1.0"])
|
25
25
|
|
26
26
|
s.author = AUTHOR
|
27
27
|
s.email = EMAIL
|
28
28
|
s.homepage = HOMEPAGE
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
namespace :test do
|
32
|
+
def execute_process(name)
|
33
|
+
sh "ruby ./spec/units/client/#{name}.rb &"
|
34
|
+
%x(ps -ef | grep #{name}).split[1]
|
35
|
+
end
|
36
|
+
def process(name)
|
37
|
+
%x(ps -ef | grep #{name} | grep -v grep).split[1] || execute_process(name)
|
38
|
+
end
|
39
|
+
def start_server_and_invoke_test(task_name)
|
40
|
+
pid = process "fake_server"
|
41
|
+
puts "fake_server pid >>>> #{pid}"
|
42
|
+
Rake::Task[task_name].invoke
|
43
|
+
sh "kill -9 #{pid}"
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Execute integration Order tests"
|
47
|
+
task :integration do
|
48
|
+
integration_path = "spec/integration/order/server"
|
49
|
+
|
50
|
+
Dir.chdir(File.join(File.dirname(__FILE__), integration_path)) do
|
51
|
+
system('rake db:drop db:create db:migrate')
|
52
|
+
system('rake')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
namespace :spec do
|
57
|
+
spec_opts = ['--options', File.join(File.dirname(__FILE__) , 'spec', 'units', 'spec.opts')]
|
58
|
+
Spec::Rake::SpecTask.new(:all) do |t|
|
59
|
+
t.spec_files = FileList['spec/units/**/*_spec.rb']
|
60
|
+
t.spec_opts = spec_opts
|
61
|
+
end
|
62
|
+
Spec::Rake::SpecTask.new(:common) do |t|
|
63
|
+
t.spec_files = FileList['spec/common/**/*_spec.rb']
|
64
|
+
t.spec_opts = spec_opts
|
65
|
+
end
|
66
|
+
Spec::Rake::SpecTask.new(:client) do |t|
|
67
|
+
t.spec_files = FileList['spec/units/client/**/*_spec.rb']
|
68
|
+
t.spec_opts = spec_opts
|
69
|
+
end
|
70
|
+
Spec::Rake::SpecTask.new(:server) do |t|
|
71
|
+
t.spec_files = FileList['spec/units/server/**/*_spec.rb']
|
72
|
+
t.spec_opts = spec_opts
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
namespace :rcov do
|
77
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
78
|
+
options_file = File.expand_path('spec/units/spec.opts')
|
79
|
+
t.spec_opts = %w(-fs -fh:doc/specs.html --color)
|
80
|
+
t.spec_files = FileList['spec/units/**/*_spec.rb']
|
81
|
+
t.rcov = true
|
82
|
+
t.rcov_opts = ["-e", "/Library*", "-e", "~/.rvm", "-e", "spec", "-i", "bin"]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
namespace :run do
|
87
|
+
task :all do
|
88
|
+
start_server_and_invoke_test('test:spec:all')
|
89
|
+
puts "Execution integration tests... (task test:integration)"
|
90
|
+
Rake::Task["test:integration"].invoke()
|
91
|
+
end
|
92
|
+
task :common do
|
93
|
+
start_server_and_invoke_test('test:spec:common')
|
94
|
+
end
|
95
|
+
task :client do
|
96
|
+
start_server_and_invoke_test('test:spec:client')
|
97
|
+
end
|
98
|
+
task :server do
|
99
|
+
start_server_and_invoke_test('test:spec:server')
|
100
|
+
end
|
101
|
+
task :rcov do
|
102
|
+
start_server_and_invoke_test('test:rcov:rcov')
|
103
|
+
end
|
104
|
+
end
|
34
105
|
end
|
35
106
|
|
36
107
|
Rake::GemPackageTask.new(spec) do |pkg|
|
37
108
|
pkg.gem_spec = spec
|
38
109
|
end
|
39
110
|
|
111
|
+
Rake::RDocTask.new("rdoc") do |rdoc|
|
112
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
113
|
+
# rdoc.rdoc_files.include('lib/**/**/*.rb')
|
114
|
+
end
|
115
|
+
|
116
|
+
begin
|
117
|
+
require 'yard'
|
118
|
+
YARD::Rake::YardocTask.new do |t|
|
119
|
+
t.files = ['lib/restfulie/**/*.rb', 'README.textile'] # optional
|
120
|
+
# t.options = ['--any', '--extra', '--opts'] # optional
|
121
|
+
end
|
122
|
+
rescue; end
|
123
|
+
|
40
124
|
desc "Install the gem locally"
|
41
125
|
task :install => [:package] do
|
42
|
-
sh %{
|
126
|
+
sh %{gem install pkg/#{GEM}-#{GEM_VERSION} -l}
|
43
127
|
end
|
44
128
|
|
45
129
|
desc "Create a gemspec file"
|
@@ -53,4 +137,5 @@ desc "Builds the project"
|
|
53
137
|
task :build => :spec
|
54
138
|
|
55
139
|
desc "Default build will run specs"
|
56
|
-
task :default => :
|
140
|
+
task :default => ['test:run:all']
|
141
|
+
|
@@ -1,65 +1,60 @@
|
|
1
|
-
|
2
|
-
# Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
-
# you may not use this file except in compliance with the License.
|
7
|
-
# You may obtain a copy of the License at
|
8
|
-
#
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
-
#
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
-
# See the License for the specific language governing permissions and
|
15
|
-
# limitations under the License.
|
16
|
-
#
|
1
|
+
module Restfulie::Client#:nodoc
|
17
2
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
type = keys[0].camelize.constantize
|
26
|
-
type.from_xml(body)
|
3
|
+
module EntryPoint
|
4
|
+
include HTTP::RequestMarshaller
|
5
|
+
extend self
|
6
|
+
|
7
|
+
@resources_configurations = {}
|
8
|
+
def configuration_of(resource_name)
|
9
|
+
@resources_configurations[resource_name]
|
27
10
|
end
|
28
|
-
end
|
29
|
-
end
|
30
11
|
|
31
|
-
|
12
|
+
def configuration_for(resource_name,configuration = Configuration.new)
|
13
|
+
yield configuration if block_given?
|
14
|
+
@resources_configurations[resource_name] = configuration
|
15
|
+
end
|
16
|
+
|
17
|
+
def retrieve(resource_name)
|
18
|
+
returning Object.new do |resource|
|
19
|
+
restore.extend(Base)
|
20
|
+
resource.configure
|
21
|
+
end
|
22
|
+
end
|
32
23
|
|
33
|
-
# will execute some action in a specific URI
|
34
|
-
def self.at(uri)
|
35
|
-
Client::RequestExecution.new(nil, nil).at uri
|
36
24
|
end
|
37
|
-
|
38
|
-
module Client
|
39
|
-
module Config
|
40
|
-
BASIC_MAPPING = { :delete => Net::HTTP::Delete, :put => Net::HTTP::Put, :get => Net::HTTP::Get, :post => Net::HTTP::Post}
|
41
|
-
DEFAULTS = { :destroy => Net::HTTP::Delete, :delete => Net::HTTP::Delete, :cancel => Net::HTTP::Delete,
|
42
|
-
:refresh => Net::HTTP::Get, :reload => Net::HTTP::Get, :show => Net::HTTP::Get, :latest => Net::HTTP::Get, :self => Net::HTTP::Get}
|
43
25
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
return BASIC_MAPPING[overriden_option.to_sym] if overriden_option
|
50
|
-
DEFAULTS[name.to_sym] || Net::HTTP::Post
|
51
|
-
end
|
52
|
-
|
26
|
+
module Base
|
27
|
+
include HTTP::RequestMarshaller
|
28
|
+
|
29
|
+
def self.included(base)#:nodoc
|
30
|
+
base.extend(self)
|
53
31
|
end
|
54
|
-
|
55
|
-
module Base
|
56
32
|
|
57
|
-
|
58
|
-
|
59
|
-
|
33
|
+
def uses_restfulie(configuration = Configuration.new,&block)
|
34
|
+
EntryPoint.configuration_for(resource_name,configuration,&block)
|
35
|
+
configure
|
36
|
+
end
|
37
|
+
|
38
|
+
def configure
|
39
|
+
configuration = EntryPoint.configuration_of(resource_name)
|
40
|
+
raise "Undefined configuration for #{resource_name}" unless configuration
|
41
|
+
at(configuration.entry_point)
|
42
|
+
configuration.representations.each do |representation_name,representation|
|
43
|
+
register_representation(representation_name,representation)
|
60
44
|
end
|
61
|
-
|
62
45
|
end
|
63
|
-
|
46
|
+
|
47
|
+
def resource_name
|
48
|
+
@resource_name || @resource_name = self.class.to_s.to_sym
|
49
|
+
end
|
50
|
+
|
64
51
|
end
|
52
|
+
|
65
53
|
end
|
54
|
+
|
55
|
+
# Shortcut to Restfulie::Client::EntryPoint
|
56
|
+
module Restfulie
|
57
|
+
include Client::EntryPoint
|
58
|
+
extend self
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Restfulie::Client #:nodoc:
|
2
|
+
|
3
|
+
# Use this class to configure the entry point and other relevant behaviors related to accessing or interacting with resources
|
4
|
+
#
|
5
|
+
# The available options are:
|
6
|
+
#
|
7
|
+
# * <tt>:entry_point</tt> - The URI for an entry point, such as http://resource.entrypoint.com/post
|
8
|
+
# * <tt>:representations</tt> - Representations.
|
9
|
+
#
|
10
|
+
# You can also store any other custom configuration.
|
11
|
+
#
|
12
|
+
# ==== Example
|
13
|
+
#
|
14
|
+
# configuration = Configuration.new
|
15
|
+
# configuration[:entry_point] = 'http://resource.entrypoint.com/post'
|
16
|
+
# configuration[:entry_point] # => 'http://resource.entrypoint.com/post'
|
17
|
+
#
|
18
|
+
# or you can use:
|
19
|
+
#
|
20
|
+
# configuration.entry_point = 'http://resource.entrypoint.com/post'
|
21
|
+
# configuration.entry_point # => 'http://resource.entrypoint.com/post'
|
22
|
+
class Configuration < ::Hash
|
23
|
+
|
24
|
+
# the current environment
|
25
|
+
attr_reader :environment
|
26
|
+
|
27
|
+
@@default_configuration = {
|
28
|
+
:entry_point => '',
|
29
|
+
:representations => {}
|
30
|
+
}
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
super
|
34
|
+
self.environment = :development
|
35
|
+
end
|
36
|
+
|
37
|
+
# this will store a new configuration (based on the default) for the environment passed by value.
|
38
|
+
def environment=(value)
|
39
|
+
@environment = value
|
40
|
+
unless has_key?(@environment)
|
41
|
+
dee_clone = Marshal::load(Marshal::dump(@@default_configuration))
|
42
|
+
store(@environment,dee_clone)
|
43
|
+
end
|
44
|
+
@environment
|
45
|
+
end
|
46
|
+
|
47
|
+
# access (key) configuration value
|
48
|
+
def [](key)
|
49
|
+
fetch(@environment)[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
# store on (key) configuration the value
|
53
|
+
def []=(key,value)
|
54
|
+
fetch(@environment)[key] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def method_missing(name, *args, &block)
|
58
|
+
method_name = name.to_s
|
59
|
+
if method_name.last == '='
|
60
|
+
fetch(environment)[method_name.chop.to_sym] = args[0]
|
61
|
+
else
|
62
|
+
value = fetch(environment)[name]
|
63
|
+
value ? value : super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|