noah 0.0.5-jruby → 0.1-jruby
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +10 -0
- data/LICENSE +201 -0
- data/README.md +68 -212
- data/Rakefile +65 -41
- data/TODO.md +65 -0
- data/bin/noah +2 -1
- data/bin/noah-watcher.rb +103 -0
- data/config.ru +6 -3
- data/config/warble.rb +18 -0
- data/examples/README.md +116 -0
- data/examples/cluster.ru +2 -0
- data/examples/custom-watcher.rb +10 -0
- data/examples/httpclient-server.rb +7 -0
- data/examples/httpclient.rb +12 -0
- data/examples/httpclient2.rb +28 -0
- data/examples/js/FABridge.js +1452 -0
- data/examples/js/WebSocketMain.swf +830 -0
- data/examples/js/swfobject.js +851 -0
- data/examples/js/web_socket.js +312 -0
- data/examples/logger.rb +11 -0
- data/examples/reconfiguring-sinatra-watcher.rb +11 -0
- data/examples/reconfiguring-sinatra.rb +33 -0
- data/examples/simple-post.rb +17 -0
- data/examples/websocket.html +24 -0
- data/examples/websocket.rb +41 -0
- data/lib/noah.rb +6 -8
- data/lib/noah/app.rb +20 -268
- data/lib/noah/application_routes.rb +70 -0
- data/lib/noah/ark.rb +0 -0
- data/lib/noah/configuration_routes.rb +81 -0
- data/lib/noah/custom_watcher.rb +79 -0
- data/lib/noah/ephemeral_routes.rb +47 -0
- data/lib/noah/helpers.rb +37 -14
- data/lib/noah/host_routes.rb +69 -0
- data/lib/noah/models.rb +86 -5
- data/lib/noah/models/applications.rb +41 -0
- data/lib/noah/models/configurations.rb +49 -0
- data/lib/noah/models/ephemerals.rb +54 -0
- data/lib/noah/models/hosts.rb +56 -0
- data/lib/noah/models/services.rb +54 -0
- data/lib/noah/models/watchers.rb +62 -0
- data/lib/noah/passthrough.rb +11 -0
- data/lib/noah/service_routes.rb +71 -0
- data/lib/noah/validations.rb +1 -0
- data/lib/noah/validations/watcher_validations.rb +48 -0
- data/lib/noah/version.rb +1 -1
- data/lib/noah/watcher_routes.rb +45 -0
- data/noah.gemspec +25 -17
- data/spec/application_spec.rb +30 -30
- data/spec/configuration_spec.rb +78 -14
- data/spec/ephemeral_spec.rb +59 -0
- data/spec/host_spec.rb +21 -21
- data/spec/noahapp_application_spec.rb +6 -6
- data/spec/noahapp_configuration_spec.rb +5 -5
- data/spec/noahapp_ephemeral_spec.rb +115 -0
- data/spec/noahapp_host_spec.rb +3 -3
- data/spec/noahapp_service_spec.rb +10 -10
- data/spec/noahapp_watcher_spec.rb +123 -0
- data/spec/service_spec.rb +27 -27
- data/spec/spec_helper.rb +13 -22
- data/spec/support/db/.keep +0 -0
- data/spec/support/test-redis.conf +8 -0
- data/spec/watcher_spec.rb +62 -0
- data/views/index.haml +21 -15
- metadata +189 -146
- data/Gemfile.lock +0 -83
- data/doc/coverage/index.html +0 -138
- data/doc/coverage/jquery-1.3.2.min.js +0 -19
- data/doc/coverage/jquery.tablesorter.min.js +0 -15
- data/doc/coverage/lib-helpers_rb.html +0 -393
- data/doc/coverage/lib-models_rb.html +0 -1449
- data/doc/coverage/noah_rb.html +0 -2019
- data/doc/coverage/print.css +0 -12
- data/doc/coverage/rcov.js +0 -42
- data/doc/coverage/screen.css +0 -270
- data/lib/noah/applications.rb +0 -46
- data/lib/noah/configurations.rb +0 -49
- data/lib/noah/hosts.rb +0 -54
- data/lib/noah/services.rb +0 -57
- data/lib/noah/watchers.rb +0 -18
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'uri'
|
3
|
+
require 'logger'
|
4
|
+
begin
|
5
|
+
require 'em-hiredis'
|
6
|
+
rescue LoadError
|
7
|
+
puts "Please install: em-hiredis"
|
8
|
+
end
|
9
|
+
|
10
|
+
@log = Logger.new(STDOUT)
|
11
|
+
@log.level = Logger::DEBUG
|
12
|
+
|
13
|
+
require File.join(File.dirname(__FILE__), 'passthrough')
|
14
|
+
|
15
|
+
module Noah
|
16
|
+
|
17
|
+
class CustomWatcher
|
18
|
+
extend Passthrough
|
19
|
+
|
20
|
+
passthrough :redis_host, :pattern, :destination, :run!, :run_watcher
|
21
|
+
|
22
|
+
attr_accessor :my_pattern, :my_destination, :my_redis
|
23
|
+
|
24
|
+
def self.watch(&blk)
|
25
|
+
watcher = Noah::Watcher.new
|
26
|
+
watcher.instance_eval(&blk) if block_given?
|
27
|
+
watcher
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@my_redis ||= ENV['REDIS_URL']
|
32
|
+
@my_pattern ||= '//noah'
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.redis_host(host)
|
36
|
+
@my_redis = host
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.pattern(pattern)
|
40
|
+
@my_pattern = pattern
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.destination(destination)
|
44
|
+
@my_destination = destination
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.run!
|
48
|
+
@my_destination.nil? ? (raise ArgumentError) : run_watcher(@my_destination)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def self.run_watcher(dest)
|
53
|
+
log = Logger.new(STDOUT)
|
54
|
+
log.level = Logger::INFO
|
55
|
+
log.debug "#{dest.inspect}"
|
56
|
+
redis_url = URI.parse(@my_redis)
|
57
|
+
db = redis_url.path.gsub(/\//,'')
|
58
|
+
|
59
|
+
EventMachine.run do
|
60
|
+
trap("TERM") { log.info "Killed"; EventMachine.stop }
|
61
|
+
trap("INT") { log.info "Interrupted"; EventMachine.stop }
|
62
|
+
channel = EventMachine::Channel.new
|
63
|
+
r = EventMachine::Hiredis::Client.connect(redis_url.host, redis_url.port)
|
64
|
+
# Pulling out dbnum for now. Need to rethink it
|
65
|
+
#log.info "Binding to pattern #{db}:#{@my_pattern}"
|
66
|
+
log.info "Binding to pattern #{@my_pattern}"
|
67
|
+
r.psubscribe("#{@my_pattern}*")
|
68
|
+
r.on(:pmessage) do |pattern, event, message|
|
69
|
+
log.debug "Got message"
|
70
|
+
channel.push "#{message}"
|
71
|
+
end
|
72
|
+
r.errback { log.info "Something went tango-uniform" }
|
73
|
+
|
74
|
+
sub = channel.subscribe {|msg| log.info "Calling message handler"; dest.call(msg)}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Noah::App
|
2
|
+
get '/e/?' do
|
3
|
+
halt 404
|
4
|
+
end
|
5
|
+
|
6
|
+
get '/e/*' do
|
7
|
+
params["splat"].size == 0 ? (halt 404) : (e=Noah::Ephemeral.find(:path => "/#{params["splat"][0]}").first)
|
8
|
+
(halt 404) if e.nil?
|
9
|
+
content_type "application/octet-stream"
|
10
|
+
e.data.nil? ? "" : "#{e.data}"
|
11
|
+
end
|
12
|
+
|
13
|
+
put '/e/*/watch' do
|
14
|
+
required_params = ["endpoint"]
|
15
|
+
data = JSON.parse(request.body.read)
|
16
|
+
(data.keys.sort == required_params.sort) ? (e = Noah::Watcher.find(:path => params[:splat][0]).first) : (raise "Missing Parameters")
|
17
|
+
e.nil? ? (halt 404) : (w = e.watch!(:endpoint => data["endpoint"]))
|
18
|
+
w.to_json
|
19
|
+
end
|
20
|
+
|
21
|
+
put '/e/*' do
|
22
|
+
raise("Data too large") if request.body.size > 512
|
23
|
+
d = request.body.read || nil
|
24
|
+
opts = {:path => "/#{params[:splat][0]}", :data => d}
|
25
|
+
e = Noah::Ephemeral.find_or_create(opts)
|
26
|
+
if e.valid?
|
27
|
+
action = e.is_new? ? "create" : "update"
|
28
|
+
r = {"action" => action, "result" => "success", "id" => e.id, "path" => e.path, "data" => e.data}
|
29
|
+
r.to_json
|
30
|
+
else
|
31
|
+
raise "#{format_errors(e)}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
delete '/e/*' do
|
36
|
+
p = params[:splat][0]
|
37
|
+
e = Noah::Ephemeral.find(:path => "/"+p).first
|
38
|
+
if e
|
39
|
+
e.delete
|
40
|
+
r = {"result" => "success", "id" => "#{e.id}", "action" => "delete", "path" => e.name}
|
41
|
+
r.to_json
|
42
|
+
else
|
43
|
+
halt 404
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/noah/helpers.rb
CHANGED
@@ -1,56 +1,79 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'models')
|
2
1
|
module Noah
|
3
2
|
module SinatraHelpers
|
4
|
-
|
3
|
+
|
4
|
+
def format_errors(model)
|
5
|
+
error_messages = model.errors.present do |e|
|
6
|
+
# Missing attributes
|
7
|
+
e.on [:name, :not_present], "Name attribute missing"
|
8
|
+
e.on [:status, :not_present], "Status attribute missing"
|
9
|
+
e.on [:format, :not_present], "Format attribute missing"
|
10
|
+
e.on [:body, :not_present], "Body attribute missing"
|
11
|
+
e.on [:application_id, :not_present], "Application attribute missing"
|
12
|
+
e.on [:path, :not_present], "Path attribute missing"
|
13
|
+
e.on [:pattern, :not_present], "Pattern attribute missing"
|
14
|
+
e.on [:endpoint, :not_present], "Endpoint attribute missing"
|
15
|
+
# Invalid option
|
16
|
+
e.on [:status, :not_member], "Status must be up, down or pending"
|
17
|
+
# Duplicate keys
|
18
|
+
e.on [[:name, :application_id], :not_unique], "Record already exists"
|
19
|
+
e.on [[:name, :host_id], :not_unique], "Record already exists"
|
20
|
+
e.on [[:endpoint, :pattern], :not_unique], "Record already exists"
|
21
|
+
e.on [:path, :not_unique], "Record already exists"
|
22
|
+
# Custom exceptions
|
23
|
+
e.on [:pattern, :already_provided], "Pattern is already provided"
|
24
|
+
e.on [:pattern, :replaces_existing], "Pattern would overwrite existing"
|
25
|
+
end
|
26
|
+
error_messages.first
|
27
|
+
end
|
5
28
|
|
6
29
|
def host(opts = {})
|
7
|
-
|
30
|
+
Noah::Host.find(opts).first
|
8
31
|
end
|
9
32
|
|
10
33
|
def hosts(opts = {})
|
11
|
-
Hosts.all(opts)
|
34
|
+
Noah::Hosts.all(opts)
|
12
35
|
end
|
13
36
|
|
14
37
|
def service(opts = {})
|
15
|
-
Service.find(options)
|
38
|
+
Noah::Service.find(options)
|
16
39
|
end
|
17
40
|
|
18
41
|
def services(opts = {})
|
19
|
-
Services.all(opts)
|
42
|
+
Noah::Services.all(opts)
|
20
43
|
end
|
21
44
|
|
22
45
|
def host_service(hostname, servicename)
|
23
|
-
h = Host.find(:name => hostname).first
|
46
|
+
h = Noah::Host.find(:name => hostname).first
|
24
47
|
if h.nil?
|
25
48
|
nil
|
26
49
|
else
|
27
|
-
Service.find(:host_id => h.id, :name => servicename).first
|
50
|
+
Noah::Service.find(:host_id => h.id, :name => servicename).first
|
28
51
|
end
|
29
52
|
end
|
30
53
|
|
31
54
|
def host_services(hostname)
|
32
|
-
h = Host.find(:name => hostname).first
|
55
|
+
h = Noah::Host.find(:name => hostname).first
|
33
56
|
if h.nil?
|
34
57
|
nil
|
35
58
|
else
|
36
|
-
Services.all(:host_id => id)
|
59
|
+
Noah::Services.all(:host_id => id)
|
37
60
|
end
|
38
61
|
end
|
39
62
|
|
40
63
|
def application(opts = {})
|
41
|
-
Application.find(opts).first
|
64
|
+
Noah::Application.find(opts).first
|
42
65
|
end
|
43
66
|
|
44
67
|
def applications(opts = {})
|
45
|
-
Applications.all(opts)
|
68
|
+
Noah::Applications.all(opts)
|
46
69
|
end
|
47
70
|
|
48
71
|
def configuration(opts = {})
|
49
|
-
Configuration.find(opts).first
|
72
|
+
Noah::Configuration.find(opts).first
|
50
73
|
end
|
51
74
|
|
52
75
|
def configurations(opts = {})
|
53
|
-
Configurations.all(opts)
|
76
|
+
Noah::Configurations.all(opts)
|
54
77
|
end
|
55
78
|
end
|
56
79
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Noah::App
|
2
|
+
# Host URIs
|
3
|
+
|
4
|
+
# GET named {Service} for named {Host}
|
5
|
+
get '/h/:hostname/:servicename/?' do |hostname, servicename|
|
6
|
+
h = host_service(hostname, servicename)
|
7
|
+
if h.nil?
|
8
|
+
halt 404
|
9
|
+
else
|
10
|
+
h.to_json
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# GET named {Host}
|
15
|
+
# @param :hostname name of {Host}
|
16
|
+
# @return [JSON] representation of {Host}
|
17
|
+
get '/h/:hostname/?' do |hostname|
|
18
|
+
h = host(:name => hostname)
|
19
|
+
if h.nil?
|
20
|
+
halt 404
|
21
|
+
else
|
22
|
+
h.to_json
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# GET all {Hosts}
|
27
|
+
get '/h/?' do
|
28
|
+
hosts.map {|h| h.to_hash}
|
29
|
+
if hosts.size == 0
|
30
|
+
halt 404
|
31
|
+
else
|
32
|
+
hosts.to_json
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
put '/h/:hostname/watch' do |hostname|
|
37
|
+
required_params = ["endpoint"]
|
38
|
+
data = JSON.parse(request.body.read)
|
39
|
+
(data.keys.sort == required_params.sort) ? (h = Noah::Host.find(:name => hostname).first) : (raise "Missing Parameters")
|
40
|
+
h.nil? ? (halt 404) : (w = h.watch!(:endpoint => data['endpoint']))
|
41
|
+
w.to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
put '/h/:hostname/?' do |hostname|
|
45
|
+
required_params = ["name", "status"]
|
46
|
+
data = JSON.parse(request.body.read)
|
47
|
+
(data.keys.sort == required_params.sort && data['name'] == hostname) ? (host = Noah::Host.find_or_create(:name => data['name'], :status => data['status'])) : (raise "Missing Parameters")
|
48
|
+
if host.valid?
|
49
|
+
r = {"result" => "success","id" => "#{host.id}","status" => "#{host.status}", "name" => "#{host.name}", "new_record" => host.is_new?}
|
50
|
+
r.to_json
|
51
|
+
else
|
52
|
+
raise "#{format_errors(host)}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
delete '/h/:hostname/?' do |hostname|
|
57
|
+
host = Noah::Host.find(:name => hostname).first
|
58
|
+
if host
|
59
|
+
services = []
|
60
|
+
Noah::Service.find(:host_id => host.id).sort.each {|x| services << x; x.delete} if host.services.size > 0
|
61
|
+
host.delete
|
62
|
+
r = {"result" => "success", "id" => "#{host.id}", "name" => "#{hostname}", "service_count" => "#{services.size}"}
|
63
|
+
r.to_json
|
64
|
+
else
|
65
|
+
halt 404
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/lib/noah/models.rb
CHANGED
@@ -1,5 +1,86 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require 'ohm'
|
2
|
+
require 'ohm/contrib'
|
3
|
+
module Noah
|
4
|
+
class Model < Ohm::Model
|
5
|
+
def self.inherited(model)
|
6
|
+
|
7
|
+
model.send :include, Ohm::Timestamping
|
8
|
+
model.send :include, Ohm::Typecast
|
9
|
+
model.send :include, Ohm::Callbacks
|
10
|
+
model.send :include, Ohm::ExtraValidations
|
11
|
+
|
12
|
+
# removing this as it's simply redundant
|
13
|
+
# model.after :save, :notify_via_redis_save
|
14
|
+
model.after :create, :notify_via_redis_create
|
15
|
+
model.after :update, :notify_via_redis_update
|
16
|
+
model.before :delete, :stash_name
|
17
|
+
model.after :delete, :notify_via_redis_delete
|
18
|
+
model.send :include, ModelClassMethods
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ModelClassMethods
|
23
|
+
|
24
|
+
def is_new?
|
25
|
+
self.created_at == self.updated_at
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def watch!(opts={:endpoint => nil, :pattern => nil})
|
30
|
+
base_pattern = "#{self.patternize_me}"
|
31
|
+
opts[:endpoint].nil? ? (raise ArgumentError, "Need an endpoint") : endpoint=opts[:endpoint]
|
32
|
+
opts[:pattern].nil? ? pattern=base_pattern : pattern=opts[:pattern]
|
33
|
+
|
34
|
+
begin
|
35
|
+
w = Watcher.new :pattern => pattern, :endpoint => endpoint
|
36
|
+
w.valid? ? w.save : (raise "#{w.errors}")
|
37
|
+
w.name
|
38
|
+
rescue Exception => e
|
39
|
+
e.message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
def patternize_me
|
45
|
+
"//noah/#{self.class_to_lower}/#{name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def stash_name
|
49
|
+
@deleted_name = self.name
|
50
|
+
end
|
51
|
+
|
52
|
+
def class_to_lower
|
53
|
+
self.class.to_s.gsub(/(.*)::(\w)/,'\2').downcase
|
54
|
+
end
|
55
|
+
def dbnum
|
56
|
+
o = Ohm.options.first
|
57
|
+
return "0" if o.nil?
|
58
|
+
return "0" if (o[:db].nil? && o[:url].nil?)
|
59
|
+
o[:db].nil? ? "#{o[:url].split('/').last}" : "#{o[:db]}"
|
60
|
+
end
|
61
|
+
|
62
|
+
["create", "update", "delete"].each do |meth|
|
63
|
+
class_eval do
|
64
|
+
define_method("notify_via_redis_#{meth}".to_sym) do
|
65
|
+
db = self.dbnum
|
66
|
+
self.name.nil? ? name=@deleted_name : name=self.name
|
67
|
+
# Pulling out dbnum for now. Need to rethink it
|
68
|
+
#pub_category = "#{db}:noah.#{self.class.to_s}[#{name}].#{meth}"
|
69
|
+
pub_category = "#{self.patternize_me}"
|
70
|
+
Ohm.redis.publish(pub_category, self.to_hash.merge({"action" => meth, "pubcategory" => pub_category}).to_json)
|
71
|
+
|
72
|
+
# The following provides a post post-action hook. It allows a class to provide it's own handling after the fact
|
73
|
+
# good example is in [Noah::Ephemeral] where it's used to check for/clean up expired ephemeral nodes entries
|
74
|
+
self.send("#{meth}_hook".to_sym) unless self.protected_methods.member?("#{meth}_hook".to_sym) == false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
require File.join(File.dirname(__FILE__), 'models','hosts')
|
82
|
+
require File.join(File.dirname(__FILE__), 'models','services')
|
83
|
+
require File.join(File.dirname(__FILE__), 'models','applications')
|
84
|
+
require File.join(File.dirname(__FILE__), 'models','configurations')
|
85
|
+
require File.join(File.dirname(__FILE__), 'models','watchers')
|
86
|
+
require File.join(File.dirname(__FILE__), 'models','ephemerals')
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'configurations')
|
2
|
+
module Noah
|
3
|
+
class Application < Model
|
4
|
+
attribute :name
|
5
|
+
collection :configurations, Configuration
|
6
|
+
|
7
|
+
index :name
|
8
|
+
|
9
|
+
def validate
|
10
|
+
super
|
11
|
+
assert_present :name
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
arr = []
|
16
|
+
configurations.sort.each {|c| arr << c.to_hash}
|
17
|
+
super.merge(:name => name, :created_at => created_at, :updated_at => updated_at, :configurations => arr)
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def find_or_create(opts = {})
|
22
|
+
begin
|
23
|
+
find(opts).first.nil? ? (app = create(opts)) : (app = find(opts).first)
|
24
|
+
if app.valid?
|
25
|
+
app.save
|
26
|
+
end
|
27
|
+
app
|
28
|
+
rescue Exception => e
|
29
|
+
e.message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class Applications
|
37
|
+
def self.all(options = {})
|
38
|
+
options.empty? ? Application.all.sort : Application.find(options).sort
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Noah
|
2
|
+
class Configuration < Model
|
3
|
+
|
4
|
+
attribute :name
|
5
|
+
attribute :format
|
6
|
+
attribute :body
|
7
|
+
attribute :new_record
|
8
|
+
reference :application, Application
|
9
|
+
|
10
|
+
index :name
|
11
|
+
index :format
|
12
|
+
index :body
|
13
|
+
|
14
|
+
def validate
|
15
|
+
super
|
16
|
+
assert_present :name
|
17
|
+
assert_present :format
|
18
|
+
assert_present :body
|
19
|
+
assert_present :application_id
|
20
|
+
assert_unique [:name, :application_id]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
Application[application_id].nil? ? app_name=nil : app_name=Application[application_id].name
|
25
|
+
super.merge(:name => name, :format => format, :body => body, :created_at => created_at, :updated_at => updated_at, :application => app_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def find_or_create(opts={})
|
30
|
+
begin
|
31
|
+
if find(opts).first.nil?
|
32
|
+
conf = create(opts)
|
33
|
+
else
|
34
|
+
conf = find(opts).first
|
35
|
+
end
|
36
|
+
rescue Exception => e
|
37
|
+
e.message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class Configurations
|
45
|
+
def self.all(options = {})
|
46
|
+
options.empty? ? Configuration.all.sort : Configuration.find(options).sort
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|