railsquest 0.1
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +26 -0
- data/Readme.md +134 -0
- data/bin/railsquest +61 -0
- data/development.log +1 -0
- data/lib/railsquest.rb +119 -0
- data/lib/railsquest/badge.rb +84 -0
- data/lib/railsquest/bonjour.rb +9 -0
- data/lib/railsquest/bonjour/advertiser.rb +66 -0
- data/lib/railsquest/bonjour/browser.rb +58 -0
- data/lib/railsquest/bonjour/person.rb +25 -0
- data/lib/railsquest/bonjour/quest.rb +39 -0
- data/lib/railsquest/bonjour/quest_browser.rb +36 -0
- data/lib/railsquest/bonjour/railsquest_browser.rb +32 -0
- data/lib/railsquest/commands.rb +53 -0
- data/lib/railsquest/grit_extensions.rb +11 -0
- data/lib/railsquest/helpers.rb +100 -0
- data/lib/railsquest/quest.rb +86 -0
- data/lib/railsquest/version.rb +3 -0
- data/railsquest.gemspec +21 -0
- data/sinatra/app.rb +162 -0
- data/sinatra/config.ru +2 -0
- data/sinatra/lib/diff_helpers.rb +92 -0
- data/sinatra/lib/mock_browsers.rb +55 -0
- data/sinatra/public/jquery-1.3.2.min.js +19 -0
- data/sinatra/public/loader.gif +0 -0
- data/sinatra/public/logo.png +0 -0
- data/sinatra/public/pbjt.swf +0 -0
- data/sinatra/public/peanut.png +0 -0
- data/sinatra/public/rm.gif +0 -0
- data/sinatra/public/theme.mp3 +0 -0
- data/sinatra/views/home.haml +32 -0
- data/sinatra/views/layout.haml +346 -0
- data/sinatra/views/readme.haml +52 -0
- data/sinatra/views/run.haml +1 -0
- data/sinatra/views/user.haml +21 -0
- metadata +103 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'dnssd'
|
2
|
+
|
3
|
+
class Railsquest::Bonjour::Advertiser
|
4
|
+
def initialize
|
5
|
+
@services = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def go!
|
9
|
+
register_app
|
10
|
+
register_quests
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def register_app
|
15
|
+
STDOUT.puts "Registering #{Railsquest.web_uri}"
|
16
|
+
tr = DNSSD::TextRecord.new
|
17
|
+
tr["name"] = Railsquest.config.name
|
18
|
+
tr["email"] = Railsquest.config.email
|
19
|
+
tr["uri"] = Railsquest.web_uri
|
20
|
+
tr["gravatar"] = Railsquest.gravatar
|
21
|
+
tr["version"] = Railsquest::VERSION
|
22
|
+
DNSSD.register("#{Railsquest.config.name}'s railsquest", "_http._tcp,_railsquest", nil, Railsquest.web_port, tr)
|
23
|
+
end
|
24
|
+
|
25
|
+
def register_quests
|
26
|
+
loop do
|
27
|
+
stop_old_services
|
28
|
+
register_new_quests
|
29
|
+
sleep(1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop_old_services
|
34
|
+
old_services.each do |old_service|
|
35
|
+
STDOUT.puts "Unregistering #{old_service.quest.uri}"
|
36
|
+
old_service.stop
|
37
|
+
@services.delete(old_service)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def old_services
|
42
|
+
@services.reject {|s| Railsquest.quests.include?(s.quest)}
|
43
|
+
end
|
44
|
+
|
45
|
+
def register_new_quests
|
46
|
+
new_quests.each do |new_quest|
|
47
|
+
STDOUT.puts "Registering #{new_quest.uri}"
|
48
|
+
tr = DNSSD::TextRecord.new
|
49
|
+
tr["name"] = new_quest.name
|
50
|
+
tr["uri"] = new_quest.uri
|
51
|
+
tr["bjour-name"] = Railsquest.config.name
|
52
|
+
tr["bjour-email"] = Railsquest.config.email
|
53
|
+
tr["bjour-uri"] = Railsquest.web_uri
|
54
|
+
tr["bjour-gravatar"] = Railsquest.gravatar
|
55
|
+
tr["bjour-version"] = Railsquest::VERSION
|
56
|
+
service = DNSSD.register(new_quest.name, "_git._tcp,_railsquest", nil, 9877, tr)
|
57
|
+
service.class.instance_eval { attr_accessor(:quest) }
|
58
|
+
service.quest = new_quest
|
59
|
+
@services << service
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_quests
|
64
|
+
Railsquest.quests.select {|quest| !@services.any? {|s| s.quest == quest } }
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'dnssd'
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
# Generic bonjour browser
|
9
|
+
#
|
10
|
+
# Example use:
|
11
|
+
#
|
12
|
+
# browser = BonjourBrowser.new("_git._tcp,_railsquest")
|
13
|
+
# loop do
|
14
|
+
# sleep(1)
|
15
|
+
# pp browser.replies.map {|r| r.name}
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Probably gem-worthy
|
19
|
+
class Railsquest::Bonjour::Browser
|
20
|
+
def initialize(service)
|
21
|
+
@service = service
|
22
|
+
@mutex = Mutex.new
|
23
|
+
@replies = {}
|
24
|
+
watch!
|
25
|
+
end
|
26
|
+
|
27
|
+
def replies
|
28
|
+
@mutex.synchronize { @replies.values }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def watch!
|
33
|
+
Thread.new(@service, @mutex, @replies) do |service, mutex, replies|
|
34
|
+
begin
|
35
|
+
DNSSD.browse!(service) do |reply|
|
36
|
+
Thread.new(reply, replies, mutex) do |reply, replies, mutex|
|
37
|
+
begin
|
38
|
+
DNSSD.resolve!(reply.name, reply.type, reply.domain) do |resolve_reply|
|
39
|
+
mutex.synchronize do
|
40
|
+
if reply.flags.add?
|
41
|
+
replies[reply.fullname] = resolve_reply
|
42
|
+
else
|
43
|
+
replies.delete(reply.fullname)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
resolve_reply.service.stop unless resolve_reply.service.stopped?
|
47
|
+
end
|
48
|
+
rescue DNSSD::BadParamError
|
49
|
+
# Ignore em
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue DNSSD::BadParamError
|
54
|
+
# Ignore em
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Railsquest::Bonjour::Person
|
2
|
+
|
3
|
+
attr_accessor :name, :email, :uri , :gravatar
|
4
|
+
|
5
|
+
def initialize(name, email, uri, gravatar)
|
6
|
+
@name, @email, @uri, @gravatar = name, email, uri, gravatar
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
self.uri == other.uri
|
11
|
+
end
|
12
|
+
|
13
|
+
def hash
|
14
|
+
to_hash.hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
{"name" => name, "email" => email, "uri" => uri, "gravatar" => gravatar}
|
19
|
+
end
|
20
|
+
|
21
|
+
def host_name
|
22
|
+
uri.gsub(/http:\/\//, '').gsub(/:9876/, '')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Railsquest::Bonjour::Quest
|
2
|
+
|
3
|
+
attr_accessor :name, :uri, :person
|
4
|
+
|
5
|
+
def initialize(name, uri, person)
|
6
|
+
@name, @uri, @person = name, uri, person
|
7
|
+
end
|
8
|
+
|
9
|
+
def html_id
|
10
|
+
Railsquest::Quest.html_id(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
self.uri == other.uri
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash
|
18
|
+
to_hash.hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def json_uri
|
22
|
+
"#{person.uri}#{name}.json"
|
23
|
+
end
|
24
|
+
|
25
|
+
def web_uri
|
26
|
+
"#{person.uri}##{html_id}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
{
|
31
|
+
"name" => name,
|
32
|
+
"uri" => uri,
|
33
|
+
"json_uri" => json_uri,
|
34
|
+
"web_uri" => web_uri,
|
35
|
+
"person" => person.to_hash
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Railsquest::Bonjour
|
2
|
+
class QuestBrowser
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@browser = Browser.new('_git._tcp,_railsquest')
|
6
|
+
end
|
7
|
+
|
8
|
+
def quests
|
9
|
+
@browser.replies.map do |reply|
|
10
|
+
Quest.new(
|
11
|
+
reply.text_record["name"],
|
12
|
+
reply.text_record["uri"],
|
13
|
+
Person.new(
|
14
|
+
reply.text_record["bjour-name"],
|
15
|
+
reply.text_record["bjour-email"],
|
16
|
+
reply.text_record["bjour-uri"],
|
17
|
+
reply.text_record["bjour-gravatar"]
|
18
|
+
)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def other_quests
|
24
|
+
quests.reject {|r| Railsquest.quests.any? {|my_quest| my_quest.name == r.name}}
|
25
|
+
end
|
26
|
+
|
27
|
+
def quests_similar_to(quest)
|
28
|
+
quests.select {|r| r.name == quest.name}
|
29
|
+
end
|
30
|
+
|
31
|
+
def quests_for(person)
|
32
|
+
quests.select {|r| r.person == person}
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Railsquest::Bonjour
|
2
|
+
class RailsquestBrowser
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@browser = Browser.new('_http._tcp,_railsquest')
|
6
|
+
end
|
7
|
+
|
8
|
+
def railsquests
|
9
|
+
@browser.replies.map do |reply|
|
10
|
+
Person.new(
|
11
|
+
reply.text_record["name"],
|
12
|
+
reply.text_record["email"],
|
13
|
+
reply.text_record["uri"],
|
14
|
+
reply.text_record["gravatar"]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def other_railsquests
|
20
|
+
railsquests.reject {|b| b.uri == Railsquest.web_uri}
|
21
|
+
end
|
22
|
+
|
23
|
+
def all_railsquests
|
24
|
+
railsquests
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_person(hostname)
|
28
|
+
railsquests.find { |u| u.uri =~ /hostname/}
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Railsquest::Commands
|
5
|
+
|
6
|
+
# Start sinatra app.
|
7
|
+
def serve_web!
|
8
|
+
puts "* Starting " + web_uri.foreground(:yellow)
|
9
|
+
fork do
|
10
|
+
ENV["RACK_ENV"] ||= "production"
|
11
|
+
require "railsquest/../../sinatra/app"
|
12
|
+
Sinatra::Application.set :port, web_port
|
13
|
+
Sinatra::Application.set :server, "thin"
|
14
|
+
Sinatra::Application.run!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def advertise!
|
19
|
+
fork { Railsquest::Bonjour::Advertiser.new.go! }
|
20
|
+
end
|
21
|
+
|
22
|
+
def add!(port, name = nil)
|
23
|
+
|
24
|
+
if name.nil?
|
25
|
+
default_name = "My Awesome Quest"
|
26
|
+
print "Quest Name?".foreground(:yellow) + " [#{default_name}] "
|
27
|
+
name = (STDIN.gets || "").strip
|
28
|
+
name = default_name if name.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
quest = Railsquest::Quest.for_name(name)
|
32
|
+
quest.port = port
|
33
|
+
|
34
|
+
if quest.exist?
|
35
|
+
abort "You've already a quest #{quest}."
|
36
|
+
end
|
37
|
+
|
38
|
+
quest.init!
|
39
|
+
|
40
|
+
puts init_success_message(quest.dirname)
|
41
|
+
|
42
|
+
quest
|
43
|
+
end
|
44
|
+
|
45
|
+
def init_success_message(quest_dirname)
|
46
|
+
plain_init_success_message(quest_dirname)
|
47
|
+
end
|
48
|
+
|
49
|
+
def plain_init_success_message(quest_dirname)
|
50
|
+
"Railsquest quest #{quest_dirname} created."
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Railsquest
|
4
|
+
module GravatarHelpers
|
5
|
+
def gravatar
|
6
|
+
gravatar_uri(self.config.email)
|
7
|
+
end
|
8
|
+
def gravatar_uri(email)
|
9
|
+
"http://gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}.png"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Lifted from Rails
|
14
|
+
module DateHelpers
|
15
|
+
# Reports the approximate distance in time between two Time or Date objects or integers as seconds.
|
16
|
+
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
|
17
|
+
# Distances are questrted based on the following table:
|
18
|
+
#
|
19
|
+
# 0 <-> 29 secs # => less than a minute
|
20
|
+
# 30 secs <-> 1 min, 29 secs # => 1 minute
|
21
|
+
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
|
22
|
+
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
|
23
|
+
# 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
|
24
|
+
# 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
|
25
|
+
# 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
|
26
|
+
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
|
27
|
+
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
|
28
|
+
# 1 yr <-> 2 yrs minus 1 secs # => about 1 year
|
29
|
+
# 2 yrs <-> max time or date # => over [2..X] years
|
30
|
+
#
|
31
|
+
# With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds:
|
32
|
+
# 0-4 secs # => less than 5 seconds
|
33
|
+
# 5-9 secs # => less than 10 seconds
|
34
|
+
# 10-19 secs # => less than 20 seconds
|
35
|
+
# 20-39 secs # => half a minute
|
36
|
+
# 40-59 secs # => less than a minute
|
37
|
+
# 60-89 secs # => 1 minute
|
38
|
+
#
|
39
|
+
# ==== Examples
|
40
|
+
# from_time = Time.now
|
41
|
+
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
|
42
|
+
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
|
43
|
+
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
|
44
|
+
# distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
|
45
|
+
# distance_of_time_in_words(from_time, 3.years.from_now) # => over 3 years
|
46
|
+
# distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
|
47
|
+
# distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
|
48
|
+
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
|
49
|
+
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
|
50
|
+
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
|
51
|
+
# distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
|
52
|
+
#
|
53
|
+
# to_time = Time.now + 6.years + 19.days
|
54
|
+
# distance_of_time_in_words(from_time, to_time, true) # => over 6 years
|
55
|
+
# distance_of_time_in_words(to_time, from_time, true) # => over 6 years
|
56
|
+
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
|
57
|
+
#
|
58
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
59
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
60
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
61
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
62
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
63
|
+
|
64
|
+
case distance_in_minutes
|
65
|
+
when 0..1
|
66
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
67
|
+
case distance_in_seconds
|
68
|
+
when 0..4 then 'less than 5 seconds'
|
69
|
+
when 5..9 then 'less than 10 seconds'
|
70
|
+
when 10..19 then 'less than 20 seconds'
|
71
|
+
when 20..39 then 'half a minute'
|
72
|
+
when 40..59 then 'less than a minute'
|
73
|
+
else '1 minute'
|
74
|
+
end
|
75
|
+
|
76
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
77
|
+
when 45..89 then 'about 1 hour'
|
78
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
79
|
+
when 1440..2879 then '1 day'
|
80
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
81
|
+
when 43200..86399 then 'about 1 month'
|
82
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
83
|
+
when 525600..1051199 then 'about 1 year'
|
84
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
|
89
|
+
#
|
90
|
+
# ==== Examples
|
91
|
+
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
|
92
|
+
# time_ago_in_words(Time.now - 15.hours) # => 15 hours
|
93
|
+
# time_ago_in_words(Time.now) # => less than a minute
|
94
|
+
#
|
95
|
+
# from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
|
96
|
+
def time_ago_in_words(from_time, include_seconds = false)
|
97
|
+
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'grit'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Railsquest
|
6
|
+
class Quest
|
7
|
+
|
8
|
+
attr_accessor :name, :port
|
9
|
+
attr_reader :secret
|
10
|
+
|
11
|
+
def self.for_name(name)
|
12
|
+
n = name.gsub(/[^A-Za-z-]+/, '_')
|
13
|
+
q = new(Railsquest.quests_path.join(n + ".quest"))
|
14
|
+
q.name = n
|
15
|
+
q
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.html_id(name)
|
19
|
+
name.gsub(/[^A-Za-z-]+/, '').downcase
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(path)
|
23
|
+
@path = Pathname(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
other.respond_to?(:path) && self.path == other.path
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :path
|
31
|
+
|
32
|
+
def exist?
|
33
|
+
path.exist?
|
34
|
+
end
|
35
|
+
|
36
|
+
def init!
|
37
|
+
secret = `uuidgen`.strip
|
38
|
+
contents = '{\"secret\" : \"' + secret + '\", \"port\" : ' + port + '}'
|
39
|
+
Dir.chdir(Railsquest.quests_path) { `echo "#{contents}" >> #{path}` }
|
40
|
+
end
|
41
|
+
|
42
|
+
def uri
|
43
|
+
Railsquest.quest_uri.gsub(/\/$/, '') + ':' + File.open(path) { |f| JSON.parse(f.gets)["port"].to_s }
|
44
|
+
end
|
45
|
+
|
46
|
+
def secret
|
47
|
+
File.open(path) { |f| JSON.parse(f.gets)["secret"] }
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
dirname.sub(".quest",'')
|
52
|
+
end
|
53
|
+
|
54
|
+
def html_id
|
55
|
+
self.class.html_id(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def dirname
|
59
|
+
path.split.last.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
name
|
64
|
+
end
|
65
|
+
|
66
|
+
def web_uri
|
67
|
+
Railsquest.web_uri + "#" + html_id
|
68
|
+
end
|
69
|
+
|
70
|
+
def attempts
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def remove!
|
75
|
+
path.rmtree
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_hash
|
79
|
+
{
|
80
|
+
"name" => name,
|
81
|
+
"uri" => uri,
|
82
|
+
"host_name" => Railsquest.host_name
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|