settler 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Reinier de Lange
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,146 @@
1
+ = Settler
2
+
3
+ Settler can be used for defining application wide settings in Rails. Settings are loaded from a YAML file and stored in the database using ActiveRecord to allow users to update settings on the fly. The YAML configuration allows you to not only specify defaults, but setting value validations as well!
4
+
5
+ == Requirements
6
+
7
+ * Activerecord, tested with Rails 2.3.8.
8
+
9
+ == Installation
10
+
11
+ Install the rails-settings gem
12
+
13
+ gem install settler
14
+
15
+ And include the gem in your apps config
16
+
17
+ config.gem 'settler'
18
+
19
+ Or install as a plugin if you must. But gems are cooler.
20
+
21
+ ./script/plugin install git://github.com/moiristo/settler.git
22
+
23
+ == Setup
24
+
25
+ * You must create the table used by the Setting model and install an initial configuration file. Simply run this command:
26
+ ruby script/generate settler
27
+
28
+ * This will create a migration and will add a settler.yml configuration file in your config directory. Now just migrate the database:
29
+ rake db:migrate
30
+
31
+ * Next, you'll have to edit the settler.yml file, of which the details are described in the next section.
32
+
33
+ * You can manually set the configuration file source and default namespace as follows:
34
+ * Settler.source = '/path/to/my/settler.yml'
35
+ * Settler.namespace = 'staging'
36
+
37
+ == Configuration
38
+
39
+ The initial version of settler.yml contains example settings for every default Rails environment. A setting consists of at least a key and a value. Consider the following example:
40
+
41
+ google_analytics_key:
42
+ alt: Google analytics key
43
+ value: 'UA-xxxxxx-x'
44
+ editable: true
45
+ deletable: false
46
+ validations:
47
+ presence: true
48
+
49
+ In this case, 'google_analytics_key' is the key of the setting. Every nested property is either an attribute of the setting or a list of validations:
50
+
51
+ * Alt, value, editable and deletable are attributes of the Setting model. If a Setting with a given key does not exist, it is created with the attributes found. Therefore, you can consider these attributes as the default values for the setting.
52
+ * Validations are not part of every setting, but are loaded on validation of a Setting instance. They apply to the value of the setting. The following validations can be created:
53
+ * 'presence', true/false.
54
+ * 'inclusion', followed by a YAML array (e.g. ['a','b','c']). Accepts a comma separated string as well.
55
+
56
+ Note that you can use ERB in the configuration file if you need to. For example:
57
+
58
+ google_analytics_key:
59
+ value: '<%= GOOGLE_ANALYTICS_KEY %>'
60
+
61
+ will evaluate the GOOGLE_ANALYTICS_KEY constant.
62
+
63
+ == Access settings
64
+
65
+ * Accessors will be created for every defined setting, returning a Setting instance:
66
+
67
+ > Settler.google_analytics_key
68
+ Setting Load (0.7ms) SELECT * FROM "settings" WHERE ("settings"."key" = 'google_analytics_key') LIMIT 1
69
+ +----+----------------------+----------------------+------------+----------+-----------+-------------------------+-------------------------+
70
+ | id | key | alt | value | editable | deletable | created_at | updated_at |
71
+ +----+----------------------+----------------------+------------+----------+-----------+-------------------------+-------------------------+
72
+ | 6 | google_analytics_key | Google analytics key | UA-xxxxx-x | true | false | 2010-09-22 15:04:06 UTC | 2010-09-22 15:04:06 UTC |
73
+ +----+----------------------+----------------------+------------+----------+-----------+-------------------------+-------------------------+
74
+
75
+ * You can access a setting's value quickly by using the index operator:
76
+
77
+ > Settler[:google_analytics_key]
78
+ Setting Load (0.7ms) SELECT * FROM "settings" WHERE ("settings"."key" = 'google_analytics_key') LIMIT 1
79
+ => "UA-xxxxx-x"
80
+
81
+ == Changing / Destroying settings
82
+
83
+ * As settings are represented by a Setting ActiveRecord model, you can just update and destroy them like you would update or destroy any AR model.
84
+
85
+ * By default, settings will only be editable or deletable iff the corresponding attribute is set to 'true'. This will be enforced before saving or destroying a record:
86
+
87
+ > Settler.google_analytics_key.destroy
88
+ Setting Load (0.7ms) SELECT * FROM "settings" WHERE ("settings"."key" = 'google_analytics_key') LIMIT 1
89
+ => false
90
+
91
+ * The Setting model performs a soft delete when it is destroyed, meaning the record is not really destroyed, but just marked as deleted. The reason for doing this is because settings are reloaded from the configuration file when your application is (re)started, unless a setting is already available in the database. Therefore, it should know about all deleted settings, otherwise it would re-insert the
92
+ deleted setting. If you want to enforce this behaviour, use Setting#delete instead.
93
+
94
+ == Advanced usage
95
+
96
+ * When you define an inclusion validation on a setting, you can access these values for use in web forms by calling 'valid_values' on the setting:
97
+
98
+ > Settler.search_algorithm.valid_values
99
+ Setting Load (0.7ms) SELECT * FROM "settings" WHERE ("settings"."key" = 'search_algorithm') LIMIT 1
100
+ => ["ferret", "sphinx"]
101
+
102
+ NB: This method returns nil when valid values cannot be found.
103
+
104
+ * Overriding setting attributes in the configuration is not as easy as it seems, since YAML doesn't support nested node merges. When overriding specific setting attributes, you should therefore do something like this:
105
+
106
+ settings: &settings
107
+ google_analytics_key: &google
108
+ alt: Google analytics key
109
+ value: 'UA-xxxxxx-x'
110
+
111
+ development:
112
+ <<: *settings
113
+ google_analytics_key:
114
+ <<: *google
115
+ alt: Development Google analytics key
116
+ value: 'UA-xxxxxx-1'
117
+
118
+ * Report missing / raise missing
119
+
120
+ * You can tell Settler to report missing attributes:
121
+
122
+ Settler.report_missing = true
123
+
124
+ This will output a warning to STDOUT and the Rails logger to notify you that a missing setting was requested.
125
+
126
+ * It is also possible to raise an exception instead when requesting missing attributes:
127
+
128
+ Settler.raise_missing = true
129
+
130
+ == To do
131
+
132
+ - Add more validations, for now only 'presence' and 'inclusion' are supported.
133
+
134
+ == Note on Patches/Pull Requests
135
+
136
+ * Fork the project.
137
+ * Make your feature addition or bug fix.
138
+ * Add tests for it. This is important so I don't break it in a
139
+ future version unintentionally.
140
+ * Commit, do not mess with rakefile, version, or history.
141
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
142
+ * Send me a pull request. Bonus points for topic branches.
143
+
144
+ == Copyright
145
+
146
+ Copyright (c) 2010 Reinier de Lange. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "settler"
8
+ gem.summary = %Q{Settler manages global application settings in Rails}
9
+ gem.description = %Q{This gem is a combination of the Squeegy's rails-settings and Binarylogic's settingslogic gem, meaning it reads its configuration from a YAML file, but stores all settings in the database as well for on the fly changes.}
10
+ gem.email = "r.j.delange@nedforce.nl"
11
+ gem.homepage = "http://github.com/moiristo/settler"
12
+ gem.authors = ["Reinier de Lange"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "settler #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,13 @@
1
+ class SettlerGenerator < Rails::Generator::NamedBase
2
+ def initialize(runtime_args, runtime_options = {})
3
+ runtime_args << 'Settler' if runtime_args.empty?
4
+ super
5
+ end
6
+
7
+ def manifest
8
+ record do |m|
9
+ m.file "settler.yml", "config/settler.yml"
10
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'add_settings_table'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ class <%= class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :settings, :force => true do |t|
4
+ t.string :key, :null => false
5
+ t.string :alt
6
+ t.text :value
7
+ t.boolean :editable
8
+ t.boolean :deletable
9
+ t.boolean :deleted
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :settings, :key
14
+ end
15
+
16
+ def self.down
17
+ drop_table :settings
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ settings: &settings
2
+ google_analytics_key: &google
3
+ alt: Google analytics key
4
+ value: 'UA-xxxxxx-x'
5
+ editable: true
6
+ deletable: false
7
+ validations:
8
+ presence: true
9
+ search_algorithm:
10
+ alt: Default search engine
11
+ value: 'ferret'
12
+ editable: true
13
+ validations:
14
+ inclusion: [ 'ferret' , 'sphinx' ]
15
+ white_list:
16
+ alt: 'White-listed words'
17
+ value: 'herp, derp'
18
+
19
+ development:
20
+ <<: *settings
21
+ google_analytics_key:
22
+ <<: *google
23
+ alt: Development Google analytics key
24
+ value: 'UA-xxxxxx-1'
25
+
26
+ test:
27
+ <<: *settings
28
+
29
+ production:
30
+ <<: *settings
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'hash_extension'
2
+ require 'setting'
3
+ require 'settler'
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ # Returns a hash that only contains keys that are passed in selected_keys. This is different
3
+ # from Hash#select, since that method returns an array of arrays.
4
+ def only(*selected_keys)
5
+ cpy = self.dup
6
+ keys.each { |key| cpy.delete(key) unless selected_keys.map(&:to_s).include?(key) }
7
+ cpy
8
+ end
9
+ end
data/lib/setting.rb ADDED
@@ -0,0 +1,57 @@
1
+ # The Setting class is an AR model that encapsulates a Settler setting. The key if the setting is the only required attribute.\
2
+ class Setting < ActiveRecord::Base
3
+ before_update :ensure_editable
4
+
5
+ validates_presence_of :key
6
+ validate :setting_validations
7
+
8
+ serialize :value
9
+
10
+ default_scope :conditions => ['deleted = ? or deleted IS NULL', false]
11
+
12
+ # Returns all valid values for this setting, which is based on the presence of an inclusion validator.
13
+ # Will return nil if no valid values could be determined.
14
+ def valid_values
15
+ if validators['inclusion']
16
+ return case
17
+ when validators['inclusion'].is_a?(Array) then validators['inclusion']
18
+ when validators['inclusion'].is_a?(String) then validators['inclusion'].to_s.split(',').map{|v| v.to_s.strip }
19
+ else nil
20
+ end
21
+ end
22
+ nil
23
+ end
24
+
25
+ # Performs a soft delete of the setting if this setting is deletable. This ensures this setting is not recreated from the configuraiton file.
26
+ # Returns false if the setting could not be destroyed.
27
+ def destroy
28
+ if deletable?
29
+ self.deleted = true if Setting.update_all({ :deleted => true }, { :id => self })
30
+ deleted?
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ # Can be used to get *all* settings, including deleted settings.
37
+ def self.without_default_scope &block
38
+ Setting.with_exclusive_scope(&block)
39
+ end
40
+
41
+ protected
42
+
43
+ # Performs instance validations as defined in the settler configuration.
44
+ def setting_validations
45
+ errors.add(:value, I18n.t(:blank, :scope => 'activerecord.errors.messages')) if validators['presence'] && ['1','true',true].include?(validators['presence']) && self.value.nil?
46
+ errors.add(:value, I18n.t(:inclusion, :scope => 'activerecord.errors.messages')) if valid_values && !valid_values.include?(self.value)
47
+ end
48
+
49
+ # Retrieves all validators defined for this setting.
50
+ def validators
51
+ @validators ||= Settler.validations_for(self.key)
52
+ end
53
+
54
+ # Before_update filter to ensure uneditable settings cannot be updated.
55
+ def ensure_editable; return false unless editable? end
56
+
57
+ end
data/lib/settler.rb ADDED
@@ -0,0 +1,77 @@
1
+ require "yaml"
2
+ require "erb"
3
+
4
+ # Settler loads and manages application wide settings and provides an interface for retrieving settings. The Settler
5
+ # object cannot be instantiated; all functionality is available on class level.
6
+ class Settler
7
+ private_class_method :new
8
+ cattr_accessor :config, :raise_missing, :report_missing
9
+ cattr_writer :namespace, :source
10
+
11
+ class << self
12
+ # Loads the settler configuration from settler.yml and defines methods for retrieving the found settings.
13
+ def load!
14
+ raise "Source settler.yml not set. Please create one and set it by using Settler.source = <file>. When using Rails, please create a settler.yml file in the config directory." unless source
15
+ self.config = YAML.load(ERB.new(File.read(source)).result).to_hash
16
+ self.config = config[namespace] if namespace
17
+ self.config.each{ |key, attributes| Setting.without_default_scope { Setting.find_or_create_by_key(key, attributes.only(:alt, :value, :editable, :deletable)) } }
18
+ Setting.all.each{ |s| key = s.key; Settler.class.send(:define_method, key){ Setting.find_by_key(key) } }
19
+ end
20
+
21
+ # Shortcut method for quickly retrieving settings. This method directly returns the setting's value instead of the Setting instance.
22
+ def [](key)
23
+ Settler.load! if config.nil?
24
+ Setting.find_by_key(key.to_s).try(:value)
25
+ end
26
+
27
+ # Returns an array of all setting keys
28
+ def settings
29
+ Settler.load! if config.nil?
30
+ Setting.all.map(&:key)
31
+ end
32
+
33
+ # Returns a list of validations to perform on a setting.
34
+ def validations_for(key)
35
+ setting_validations = {}
36
+ Settler.load! if config.nil?
37
+ (setting = config[key.to_s]) ? setting['validations'] || {} : {}
38
+ end
39
+
40
+ # Overrides the normal method_missing to return nil for non-existant settings. The behaviour of this method
41
+ # depends on the boolean attributes raise_missing and report_missing.
42
+ def method_missing(name, *args, &block)
43
+ method_name = name.to_s
44
+ return super if Settler.private_methods(false).include?(method_name)
45
+
46
+ if config.nil?
47
+ Settler.load!
48
+ return send(method_name)
49
+ end
50
+
51
+ if report_missing
52
+ puts "[Settler] Warning: setting missing: #{method_name}"
53
+ Rails.logger.warn("[Settler] setting missing: #{method_name}") if defined?(Rails)
54
+ end
55
+
56
+ raise "[Settler] setting missing: #{method_name}" if raise_missing
57
+
58
+ nil
59
+ end
60
+
61
+ # Returns the file location of the settler.yml configuration file. Defaults to
62
+ # the 'config/settler.yml' if the library is included in a Rails app.
63
+ def source
64
+ @@source ||= File.join(Rails.root, 'config', 'settler.yml') if defined?(Rails)
65
+ @@source
66
+ end
67
+
68
+ # Returns the namespace of the configuration to look for settings. Defaults to
69
+ # the current Rails environment if the library is included in a Rails app.
70
+ def namespace
71
+ @@namespace ||= Rails.env if defined?(Rails)
72
+ @@namespace
73
+ end
74
+
75
+ end
76
+
77
+ end
data/test/database.yml ADDED
@@ -0,0 +1,6 @@
1
+ sqlite:
2
+ adapter: sqlite
3
+ database: ":memory:"
4
+ sqlite3:
5
+ adapter: sqlite3
6
+ database: ":memory:"