rflow 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/Gemfile +5 -0
- data/NOTES +187 -0
- data/README +0 -0
- data/Rakefile +16 -0
- data/bin/rflow +215 -0
- data/example/basic_config.rb +49 -0
- data/example/basic_extensions.rb +142 -0
- data/example/http_config.rb +21 -0
- data/example/http_extensions.rb +262 -0
- data/lib/rflow.rb +440 -0
- data/lib/rflow/component.rb +192 -0
- data/lib/rflow/component/port.rb +150 -0
- data/lib/rflow/components.rb +10 -0
- data/lib/rflow/components/raw.rb +26 -0
- data/lib/rflow/components/raw/extensions.rb +18 -0
- data/lib/rflow/configuration.rb +290 -0
- data/lib/rflow/configuration/component.rb +27 -0
- data/lib/rflow/configuration/connection.rb +98 -0
- data/lib/rflow/configuration/migrations/20010101000001_create_settings.rb +14 -0
- data/lib/rflow/configuration/migrations/20010101000002_create_components.rb +19 -0
- data/lib/rflow/configuration/migrations/20010101000003_create_ports.rb +24 -0
- data/lib/rflow/configuration/migrations/20010101000004_create_connections.rb +27 -0
- data/lib/rflow/configuration/port.rb +30 -0
- data/lib/rflow/configuration/ruby_dsl.rb +183 -0
- data/lib/rflow/configuration/setting.rb +67 -0
- data/lib/rflow/configuration/uuid_keyed.rb +18 -0
- data/lib/rflow/connection.rb +59 -0
- data/lib/rflow/connections.rb +2 -0
- data/lib/rflow/connections/zmq_connection.rb +101 -0
- data/lib/rflow/message.rb +191 -0
- data/lib/rflow/port.rb +4 -0
- data/lib/rflow/util.rb +19 -0
- data/lib/rflow/version.rb +3 -0
- data/rflow.gemspec +42 -0
- data/schema/message.avsc +36 -0
- data/schema/raw.avsc +9 -0
- data/spec/fixtures/config_ints.rb +61 -0
- data/spec/fixtures/extensions_ints.rb +141 -0
- data/spec/rflow_configuration_spec.rb +73 -0
- data/spec/rflow_message_data_raw.rb +26 -0
- data/spec/rflow_message_data_spec.rb +60 -0
- data/spec/rflow_message_spec.rb +182 -0
- data/spec/rflow_spec.rb +100 -0
- data/spec/schema_spec.rb +28 -0
- data/spec/spec_helper.rb +37 -0
- data/temp.rb +295 -0
- metadata +270 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'rflow/configuration/uuid_keyed'
|
3
|
+
|
4
|
+
class RFlow
|
5
|
+
class Configuration
|
6
|
+
class Component < ConfigDB
|
7
|
+
include UUIDKeyed
|
8
|
+
include ActiveModel::Validations
|
9
|
+
|
10
|
+
class ComponentInvalid < StandardError; end
|
11
|
+
class ComponentNotFound < StandardError; end
|
12
|
+
|
13
|
+
serialize :options, Hash
|
14
|
+
|
15
|
+
has_many :input_ports, :primary_key => 'uuid', :foreign_key => 'component_uuid'
|
16
|
+
has_many :output_ports, :primary_key => 'uuid', :foreign_key => 'component_uuid'
|
17
|
+
|
18
|
+
#TODO: Get this to work
|
19
|
+
#has_many :input_connections, :through => :input_ports, :source => :input_connections
|
20
|
+
#has_many :output_connections, :through => :output_ports, :source => :output_connection
|
21
|
+
|
22
|
+
|
23
|
+
validates_uniqueness_of :name
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'rflow/configuration/uuid_keyed'
|
3
|
+
|
4
|
+
class RFlow
|
5
|
+
class Configuration
|
6
|
+
class Connection < ConfigDB
|
7
|
+
class ConnectionInvalid < StandardError; end
|
8
|
+
|
9
|
+
include UUIDKeyed
|
10
|
+
include ActiveModel::Validations
|
11
|
+
|
12
|
+
serialize :options, Hash
|
13
|
+
|
14
|
+
belongs_to :input_port, :primary_key => 'uuid', :foreign_key => 'input_port_uuid'
|
15
|
+
belongs_to :output_port,:primary_key => 'uuid', :foreign_key => 'output_port_uuid'
|
16
|
+
|
17
|
+
before_create :merge_default_options!
|
18
|
+
|
19
|
+
validates_uniqueness_of :uuid
|
20
|
+
validates_presence_of :output_port_uuid, :input_port_uuid
|
21
|
+
|
22
|
+
validate :all_required_options_present
|
23
|
+
|
24
|
+
|
25
|
+
def all_required_options_present
|
26
|
+
self.class.required_options.each do |option_name|
|
27
|
+
unless self.options.include? option_name.to_s
|
28
|
+
errors.add(:options, "must include #{option_name} for #{self.class.to_s}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def merge_default_options!
|
35
|
+
self.options ||= {}
|
36
|
+
self.class.default_options.each do |option_name, default_value_or_proc|
|
37
|
+
self.options[option_name.to_s] ||= default_value_or_proc.is_a?(Proc) ? default_value_or_proc.call(self) : default_value_or_proc
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Should return a list of require option names which will be
|
43
|
+
# used in validations. To be overridden.
|
44
|
+
def self.required_options; []; end
|
45
|
+
|
46
|
+
|
47
|
+
# Should return a hash of default options, where the keys are
|
48
|
+
# the option names and the values are either default option
|
49
|
+
# values or Procs that take a single connection argument. This
|
50
|
+
# allow defaults to use other parameters in the connection to
|
51
|
+
# construct the appropriate default value.
|
52
|
+
def self.default_options; {}; end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# STI Subclass for ZMQ connections and their required options
|
58
|
+
class ZMQConnection < Connection
|
59
|
+
|
60
|
+
def self.default_options
|
61
|
+
{
|
62
|
+
'output_socket_type' => 'PUSH',
|
63
|
+
'output_address' => lambda{|conn| "ipc://rflow.#{conn.uuid}"},
|
64
|
+
'output_responsibility' => 'bind',
|
65
|
+
'input_socket_type' => 'PULL',
|
66
|
+
'input_address' => lambda{|conn| "ipc://rflow.#{conn.uuid}"},
|
67
|
+
'input_responsibility' => 'connect',
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# STI Subclass for AMQP connections and their required options
|
74
|
+
class AMQPConnection < Connection
|
75
|
+
|
76
|
+
def self.default_options
|
77
|
+
{
|
78
|
+
'host' => 'localhost',
|
79
|
+
'port' => 5672,
|
80
|
+
'insist' => true,
|
81
|
+
'vhost' => '/',
|
82
|
+
'username' => 'guest',
|
83
|
+
'password' => 'guest',
|
84
|
+
|
85
|
+
# If a queue is created, these are the default parameters
|
86
|
+
# for said queue type
|
87
|
+
'queue_passive' => false,
|
88
|
+
'queue_durable' => true,
|
89
|
+
'queue_exclusive' => false,
|
90
|
+
'queue_auto_delete' => false,
|
91
|
+
'queue_nowait' => true,
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateComponents < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table(:components, :id => false) do |t|
|
4
|
+
t.string :uuid, :limit => 36, :primary => true
|
5
|
+
t.string :name
|
6
|
+
t.boolean :managed, :default => true
|
7
|
+
t.text :specification
|
8
|
+
t.text :options
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :components, :uuid, :unique => true
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :components
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class CreatePorts < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table(:ports, :id => false) do |t|
|
4
|
+
t.string :uuid, :limit => 36, :primary => true
|
5
|
+
t.string :name
|
6
|
+
|
7
|
+
# For STI
|
8
|
+
t.text :type
|
9
|
+
|
10
|
+
# UUID version of belongs_to :component
|
11
|
+
t.string :component_uuid
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :ports, :uuid, :unique => true
|
17
|
+
add_index :ports, :component_uuid
|
18
|
+
add_index :ports, [:component_uuid, :name], :unique => true
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.down
|
22
|
+
drop_table :ports
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class CreateConnections < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table(:connections, :id => false) do |t|
|
4
|
+
t.string :uuid, :limit => 36, :primary => false
|
5
|
+
t.string :name
|
6
|
+
|
7
|
+
# To allow for multiple types of connections
|
8
|
+
t.string :type
|
9
|
+
|
10
|
+
# Data flows from an output port to an input port
|
11
|
+
t.string :output_port_uuid
|
12
|
+
t.string :output_port_key, :default => '0'
|
13
|
+
t.string :input_port_uuid
|
14
|
+
t.string :input_port_key, :default => '0'
|
15
|
+
|
16
|
+
t.text :options
|
17
|
+
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
|
21
|
+
add_index :connections, :uuid, :unique => true
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.down
|
25
|
+
drop_table :connections
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'rflow/configuration/uuid_keyed'
|
3
|
+
|
4
|
+
class RFlow
|
5
|
+
class Configuration
|
6
|
+
class Port < ConfigDB
|
7
|
+
include UUIDKeyed
|
8
|
+
include ActiveModel::Validations
|
9
|
+
|
10
|
+
class PortInvalid < StandardError; end
|
11
|
+
|
12
|
+
belongs_to :component, :primary_key => 'uuid', :foreign_key => 'component_uuid'
|
13
|
+
|
14
|
+
# TODO: Make some sort of component/port validation work here
|
15
|
+
#validate :component_has_defined_port
|
16
|
+
end
|
17
|
+
|
18
|
+
# STI-based classes
|
19
|
+
class InputPort < Port;
|
20
|
+
has_many :input_connections, :class_name => 'RFlow::Configuration::Connection', :primary_key => 'uuid', :foreign_key => 'input_port_uuid'
|
21
|
+
has_many :output_ports, :through => :connections
|
22
|
+
end
|
23
|
+
|
24
|
+
class OutputPort < Port;
|
25
|
+
has_many :output_connections, :class_name => 'RFlow::Configuration::Connection', :primary_key => 'uuid', :foreign_key => 'output_port_uuid'
|
26
|
+
has_many :input_ports, :through => :connections
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'rflow/configuration'
|
2
|
+
|
3
|
+
class RFlow
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
# Ruby DSL config file controller.
|
7
|
+
# TODO: more docs and examples
|
8
|
+
class RubyDSL
|
9
|
+
attr_accessor :setting_specs, :component_specs, :connection_specs, :allocated_system_ports
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@setting_specs = []
|
13
|
+
@component_specs = []
|
14
|
+
@connection_specs = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Helper function to extract the line of the config that
|
18
|
+
# specified the operation. Useful in printing helpful error messages
|
19
|
+
def get_config_line(call_history)
|
20
|
+
call_history.first.split(':in').first
|
21
|
+
end
|
22
|
+
|
23
|
+
# DSL method to specify a name/value pair. RFlow core uses the
|
24
|
+
# 'rflow.' prefix on all of its settings. Custom settings
|
25
|
+
# should use a custom (unique) prefix
|
26
|
+
def setting(setting_name, setting_value)
|
27
|
+
setting_specs << {:name => setting_name.to_s, :value => setting_value.to_s, :config_line => get_config_line(caller)}
|
28
|
+
end
|
29
|
+
|
30
|
+
# DSL method to specify a component. Expects a name,
|
31
|
+
# specification, and set of component specific options, that
|
32
|
+
# must be marshallable into the database (i.e. should all be strings)
|
33
|
+
def component(component_name, component_specification, component_options={})
|
34
|
+
component_specs << {
|
35
|
+
:name => component_name,
|
36
|
+
:specification => component_specification.to_s, :options => component_options,
|
37
|
+
:config_line => get_config_line(caller)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# DSL method to specify a connection between a
|
42
|
+
# component/output_port and another component/input_port. The
|
43
|
+
# component/port specification is a string where the names of
|
44
|
+
# the two elements are separated by '#', and the "connection" is
|
45
|
+
# specified by a Ruby Hash, i.e.:
|
46
|
+
# connect 'componentA#output' => 'componentB#input'
|
47
|
+
# Array ports are specified with an key suffix in standard
|
48
|
+
# progamming syntax, i.e.
|
49
|
+
# connect 'componentA#arrayport[2]' => 'componentB#in[1]'
|
50
|
+
# Uses the model to assign random UUIDs
|
51
|
+
def connect(connection_hash)
|
52
|
+
config_file_line = get_config_line(caller)
|
53
|
+
connection_hash.each do |output_string, input_string|
|
54
|
+
output_component_name, output_port_name, output_port_key = parse_connection_string(output_string)
|
55
|
+
input_component_name, input_port_name, input_port_key = parse_connection_string(input_string)
|
56
|
+
|
57
|
+
connection_specs << {
|
58
|
+
:name => output_string + '=>' + input_string,
|
59
|
+
:output_component_name => output_component_name,
|
60
|
+
:output_port_name => output_port_name, :output_port_key => output_port_key,
|
61
|
+
:output_string => output_string,
|
62
|
+
:input_component_name => input_component_name,
|
63
|
+
:input_port_name => input_port_name, :input_port_key => input_port_key,
|
64
|
+
:input_string => input_string,
|
65
|
+
:config_line => config_file_line,
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Splits the connection string into component/port parts
|
71
|
+
COMPONENT_PORT_STRING_REGEX = /^(\w+)#(\w+)(?:\[([^\]]+)\])?$/
|
72
|
+
def parse_connection_string(connection_string)
|
73
|
+
matched = COMPONENT_PORT_STRING_REGEX.match(connection_string)
|
74
|
+
raise ArgumentError, "Invalid component/port string specification: #{connection_string}" unless matched
|
75
|
+
# component_name, port_name, port_key
|
76
|
+
[matched[1], matched[2], (matched[3] || nil)]
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Method to process the 'DSL' objects into the config database
|
81
|
+
# via ActiveRecord
|
82
|
+
def process
|
83
|
+
process_setting_specs
|
84
|
+
process_component_specs
|
85
|
+
process_connection_specs
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# Iterates through each setting specified in the DSL and
|
90
|
+
# creates rows in the database corresponding to the setting
|
91
|
+
def process_setting_specs
|
92
|
+
setting_specs.each do |setting_spec|
|
93
|
+
RFlow.logger.debug "Found config file setting '#{setting_spec[:name]}' = (#{Dir.getwd}) '#{setting_spec[:value]}'"
|
94
|
+
RFlow::Configuration::Setting.create! :name => setting_spec[:name], :value => setting_spec[:value]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Iterates through each component specified in the DSL and
|
100
|
+
# creates rows in the database corresponding to the component.
|
101
|
+
def process_component_specs
|
102
|
+
component_specs.each do |component_spec|
|
103
|
+
RFlow.logger.debug "Found component '#{component_spec[:name]}', creating"
|
104
|
+
RFlow::Configuration::Component.create! :name => component_spec[:name], :specification => component_spec[:specification], :options => component_spec[:options]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Iterates through each component specified in the DSL and uses
|
110
|
+
# 'process_connection' to insert all the parts of the connection
|
111
|
+
# into the database
|
112
|
+
def process_connection_specs
|
113
|
+
connection_specs.each do |connection_spec|
|
114
|
+
process_connection_spec(connection_spec)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# For the given connection, break up each input/output
|
119
|
+
# component/port specification, ensure that the component
|
120
|
+
# already exists in the database (by name). Also, only supports
|
121
|
+
# ZeroMQ ipc sockets
|
122
|
+
def process_connection_spec(connection_spec)
|
123
|
+
RFlow.logger.debug "Found connection from '#{connection_spec[:output_string]}' to '#{connection_spec[:input_string]}', creating"
|
124
|
+
|
125
|
+
# an input port can be associated with multiple outputs, but
|
126
|
+
# an output port can only be associated with one input
|
127
|
+
output_component = RFlow::Configuration::Component.find_by_name connection_spec[:output_component_name]
|
128
|
+
raise RFlow::Configuration::Component::ComponentNotFound, "#{connection_spec[:output_component_name]}" unless output_component
|
129
|
+
output_port = output_component.output_ports.find_or_initialize_by_name :name => connection_spec[:output_port_name]
|
130
|
+
output_port.save!
|
131
|
+
|
132
|
+
input_component = RFlow::Configuration::Component.find_by_name connection_spec[:input_component_name]
|
133
|
+
raise RFlow::Configuration::Component::ComponentNotFound, "#{connection_spec[:input_component_name]}" unless input_component
|
134
|
+
input_port = input_component.input_ports.find_or_initialize_by_name :name => connection_spec[:input_port_name]
|
135
|
+
input_port.save!
|
136
|
+
|
137
|
+
# Create a unique ZMQ address
|
138
|
+
# zmq_address = "ipc://run/rflow.#{output_component.uuid}.#{output_port.uuid}"
|
139
|
+
# if connection_spec[:output_port_key]
|
140
|
+
# zmq_address << ".#{connection_spec[:output_port_key].gsub(/[^\w]/, '').downcase}"
|
141
|
+
# end
|
142
|
+
|
143
|
+
connection = RFlow::Configuration::ZMQConnection.new(:name => connection_spec[:name],
|
144
|
+
:output_port_key => connection_spec[:output_port_key],
|
145
|
+
:input_port_key => connection_spec[:input_port_key])
|
146
|
+
# :options => {
|
147
|
+
# 'output_socket_type' => "PUSH",
|
148
|
+
# 'output_address' => zmq_address,
|
149
|
+
# 'output_responsibility' => "bind",
|
150
|
+
# 'input_socket_type' => "PULL",
|
151
|
+
# 'input_address' => zmq_address,
|
152
|
+
# 'input_responsibility' => "connect",
|
153
|
+
# })
|
154
|
+
|
155
|
+
connection.output_port = output_port
|
156
|
+
connection.input_port = input_port
|
157
|
+
connection.save!
|
158
|
+
|
159
|
+
rescue RFlow::Configuration::Component::ComponentNotFound => e
|
160
|
+
error_message = "Component '#{e.message}' not found at #{connection_spec[:config_line]}"
|
161
|
+
RFlow.logger.error error_message
|
162
|
+
raise RFlow::Configuration::Connection::ConnectionInvalid, error_message
|
163
|
+
rescue Exception => e
|
164
|
+
# TODO: Figure out why an ArgumentError doesn't put the
|
165
|
+
# offending message into e.message, even though it is printed
|
166
|
+
# out if not caught
|
167
|
+
error_message = "#{e.class}: #{e.message} at config '#{connection_spec[:config_line]}'"
|
168
|
+
RFlow.logger.debug "Exception #{e.class} - " + error_message
|
169
|
+
RFlow.logger.error error_message
|
170
|
+
raise RFlow::Configuration::Connection::ConnectionInvalid, error_message
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
# Method called within the config file itself
|
175
|
+
def self.configure
|
176
|
+
config_file = self.new
|
177
|
+
yield config_file
|
178
|
+
config_file.process
|
179
|
+
end
|
180
|
+
|
181
|
+
end # class RubyDSL
|
182
|
+
end # class Configuration
|
183
|
+
end # class RFlow
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'rflow/configuration/uuid_keyed'
|
3
|
+
|
4
|
+
class RFlow
|
5
|
+
class Configuration
|
6
|
+
class Setting < ConfigDB
|
7
|
+
class SettingInvalid < StandardError; end
|
8
|
+
|
9
|
+
include ActiveModel::Validations
|
10
|
+
|
11
|
+
set_primary_key :name
|
12
|
+
attr_accessible :name, :value
|
13
|
+
|
14
|
+
DEFAULTS = {
|
15
|
+
'rflow.application_name' => 'rflow',
|
16
|
+
|
17
|
+
'rflow.application_directory_path' => '.',
|
18
|
+
'rflow.pid_directory_path' => 'run', #lambda {File.join(Setting['rflow.application_directory_path'], 'run')},
|
19
|
+
'rflow.log_directory_path' => 'log', #lambda {File.join(Setting['rflow.application_directory_path'], 'log')},
|
20
|
+
|
21
|
+
'rflow.log_file_path' => lambda {File.join(Setting['rflow.log_directory_path'], Setting['rflow.application_name'] + '.log')},
|
22
|
+
'rflow.pid_file_path' => lambda {File.join(Setting['rflow.pid_directory_path'], Setting['rflow.application_name'] + '.pid')},
|
23
|
+
|
24
|
+
'rflow.log_level' => 'INFO',
|
25
|
+
}
|
26
|
+
|
27
|
+
DIRECTORY_PATHS = [
|
28
|
+
'rflow.application_directory_path',
|
29
|
+
'rflow.pid_directory_path',
|
30
|
+
'rflow.log_directory_path',
|
31
|
+
]
|
32
|
+
FILE_PATHS = [
|
33
|
+
'rflow.log_file_path',
|
34
|
+
'rflow.pid_file_path',
|
35
|
+
]
|
36
|
+
|
37
|
+
|
38
|
+
# TODO: fix these validations, as they run without the
|
39
|
+
#application directory path context for subdirectories
|
40
|
+
#validate :valid_directory_path, :if => :directory_path?
|
41
|
+
#validate :valid_writable_path, :if => :directory_path?
|
42
|
+
|
43
|
+
# TODO: Think about making this a regex check to pull in other,
|
44
|
+
# externally-defined settings
|
45
|
+
def directory_path?
|
46
|
+
DIRECTORY_PATHS.include? self.name
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid_directory_path
|
50
|
+
unless File.directory? self.value
|
51
|
+
errors.add :value, "setting '#{self.name}' is not a directory ('#{File.expand_path self.value}')"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid_writable_path
|
56
|
+
unless File.writable? self.value
|
57
|
+
errors.add :value, "setting '#{self.name}' is not writable ('#{File.expand_path self.value}')"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.[](setting_name)
|
62
|
+
Setting.find(setting_name).value rescue nil
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|