settings_lord 1.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/MIT-LICENSE +20 -0
- data/README.md +99 -0
- data/Rakefile +1 -0
- data/lib/generators/settings_lord/settings_lord_generator.rb +16 -0
- data/lib/generators/settings_lord/templates/migration.rb +14 -0
- data/lib/generators/settings_lord/templates/setting.rb +2 -0
- data/lib/settings_lord/active_record.rb +43 -0
- data/lib/settings_lord/base.rb +22 -0
- data/lib/settings_lord/meta_setting.rb +120 -0
- data/lib/settings_lord/meta_setting_collection.rb +99 -0
- data/lib/settings_lord/reflector.rb +91 -0
- data/lib/settings_lord/setting_creator.rb +90 -0
- data/lib/settings_lord/version.rb +11 -0
- data/lib/settings_lord.rb +17 -0
- data/settings_lord.gemspec +17 -0
- data/test/settings_lord_test.rb +226 -0
- data/test/test_helper.rb +20 -0
- metadata +74 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 [name of plugin creator]
|
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.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
#SettingsLord
|
2
|
+
Easy way to manage your site settings
|
3
|
+
|
4
|
+
##Why?
|
5
|
+
Because we should create more cms-ready gems for rails.
|
6
|
+
|
7
|
+
##Requirements
|
8
|
+
Rails 3 only
|
9
|
+
|
10
|
+
##Install
|
11
|
+
add to your Gemfile:
|
12
|
+
|
13
|
+
gem 'settings_lord'
|
14
|
+
|
15
|
+
##Overview
|
16
|
+
You can create settings via class methods:
|
17
|
+
|
18
|
+
class Setting < ActiveRecord::Base
|
19
|
+
|
20
|
+
settings do
|
21
|
+
site_name :default => "RockBlogger"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
After this you will be able to manipulate this setting:
|
27
|
+
|
28
|
+
Setting.site_name # => "RockBlogger"
|
29
|
+
Setting.site_name = "My blog about software"
|
30
|
+
Setting.site_name # => "My blog about software"
|
31
|
+
|
32
|
+
|
33
|
+
You can store your settings in namespaces:
|
34
|
+
|
35
|
+
# in class body
|
36
|
+
settings :site do
|
37
|
+
name :default => "RockBlogger"
|
38
|
+
end
|
39
|
+
|
40
|
+
# any other place
|
41
|
+
Setting.site.name # => "RockBlogger"
|
42
|
+
Setting.site.name = "Rails notes"
|
43
|
+
Setting.site.name # => "Rails notes"
|
44
|
+
|
45
|
+
|
46
|
+
What about settings freezing?
|
47
|
+
|
48
|
+
settings :site do
|
49
|
+
developed_by :default => "Pechorin Andrey", :as_frozen => true
|
50
|
+
end
|
51
|
+
|
52
|
+
Setting.site.created_by # => "Pechorin Andrey"
|
53
|
+
Setting.site.created_by = "some other person" # => will raise NoMethodError
|
54
|
+
|
55
|
+
You can cast values in many ways:
|
56
|
+
|
57
|
+
settings do
|
58
|
+
year :default => 1990, :cast => lambda {|value| value.to_s + " year is now!"}
|
59
|
+
should_be_integer :default => 10 # will store 10 as string in database, but automatically create cast symbol -> :to_i
|
60
|
+
should_be_integer_with_cast :default => '10', :cast => :to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
Setting.year # => "1990 year is now!"
|
64
|
+
Setting.year = 2011
|
65
|
+
Setting.year # => "2011 year is now!"
|
66
|
+
|
67
|
+
Setting.should_be_integer.class # => Fixnum
|
68
|
+
Setting.should_be_integer_with_cast.class # => Fixnum
|
69
|
+
|
70
|
+
What about booleans settings?
|
71
|
+
|
72
|
+
settings :blog_settings do
|
73
|
+
comments_are_closed :default => false, :as_boolean => true
|
74
|
+
end
|
75
|
+
|
76
|
+
Setting.blog_settings.comments_are_closed # => false
|
77
|
+
Setting.blog_settings.comments_are_closed = 10 # will cast 10 to true/false value ;)
|
78
|
+
Setting.blog_settings.comments_are_closed # => true
|
79
|
+
|
80
|
+
You can limit accepted values:
|
81
|
+
|
82
|
+
settings do
|
83
|
+
posts_per_page :accepted_values => 2..20 # Range
|
84
|
+
locale :accepted_values => [:ru,:en] # Array
|
85
|
+
support_email :accepted_values => /support@regexp/ # Regexp
|
86
|
+
end
|
87
|
+
|
88
|
+
Setting.posts_per_page = 12 # ok
|
89
|
+
Setting.posts_per_page = 30 # => will raise Exception
|
90
|
+
|
91
|
+
All settings stored in database, but you can use in-memory settings
|
92
|
+
|
93
|
+
settings do
|
94
|
+
memory_option :storage => :memory
|
95
|
+
end
|
96
|
+
|
97
|
+
##What next?
|
98
|
+
* integration with other storages
|
99
|
+
* \_default\_value, \_before\_cast
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class SettingsLordGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
def generate_settings_lord
|
8
|
+
copy_file "migration.rb", "db/migrate/#{migration_time}_create_settings.rb"
|
9
|
+
copy_file "setting.rb", "app/models/setting.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
def migration_time
|
13
|
+
Time.now.strftime('%Y%m%d%H%M%S')
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SettingsLord::ActiveRecord
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
def settings(namespace = nil,*args,&block)
|
6
|
+
SettingsLord.check_active_record!
|
7
|
+
self.send(:extend, SettingsLord::ActiveRecord::MethodMissing)
|
8
|
+
|
9
|
+
SettingsLord.setting_creator.klass = self
|
10
|
+
SettingsLord.setting_creator.parent = create_parent_by_namespace(namespace)
|
11
|
+
SettingsLord.setting_creator.instance_eval &block
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_parent_by_namespace(namespace)
|
15
|
+
namespace ? Setting.find_or_create_by_name_and_klass_and_parent_id_and_value(namespace.to_s.underscore, self.model_name.underscore, nil, nil) : nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_parent_by_namespace(namespace,klass)
|
19
|
+
Setting.find_by_name_and_parent_id_and_klass(namespace.to_s,nil,klass)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
module MethodMissing
|
25
|
+
|
26
|
+
def method_missing(name,*args,&block)
|
27
|
+
result = SettingsLord::Reflector.new(:name => name, :new_value => args.first, :klass => self).reflect
|
28
|
+
|
29
|
+
if result.is_a? SettingsLord::MetaSetting
|
30
|
+
return result.get_value
|
31
|
+
# for setters
|
32
|
+
elsif result.present?
|
33
|
+
return result
|
34
|
+
# for others
|
35
|
+
else
|
36
|
+
super(name,*args,&block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SettingsLord
|
2
|
+
|
3
|
+
ACTIVE_RECORD_COLUMNS = ['name','value','klass','parent_id']
|
4
|
+
|
5
|
+
mattr_accessor :setting_creator
|
6
|
+
mattr_accessor :meta_settings
|
7
|
+
|
8
|
+
def self.check_active_record!
|
9
|
+
ACTIVE_RECORD_COLUMNS.each do |column|
|
10
|
+
unless Setting.column_names.include?(column)
|
11
|
+
raise Exception, "Column '#{column}' doesn't exists in class #{self.name}!"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Objects pool
|
17
|
+
def self.setup_plugin!
|
18
|
+
self.meta_settings ||= MetaSettingCollection.new
|
19
|
+
self.setting_creator ||= SettingCreator.new
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
class SettingsLord::MetaSetting
|
2
|
+
|
3
|
+
def initialize(*args)
|
4
|
+
setup_attributes!(args)
|
5
|
+
setup_active_record_object!
|
6
|
+
end
|
7
|
+
|
8
|
+
def similar_to?(target)
|
9
|
+
if target.respond_to? :parent
|
10
|
+
@name == target.name && @klass == target.klass && @parent == target.parent
|
11
|
+
else
|
12
|
+
@name == target.name && @klass == target.klass
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_value(new_value)
|
17
|
+
raise NoMethodError if @as_frozen == true
|
18
|
+
|
19
|
+
case @accepted_values
|
20
|
+
when Range, Array
|
21
|
+
raise Exception unless @accepted_values.include?(new_value)
|
22
|
+
when Regexp
|
23
|
+
raise Exception unless !!(new_value.match(@accepted_values))
|
24
|
+
end
|
25
|
+
|
26
|
+
case @storage
|
27
|
+
when :active_record
|
28
|
+
record = Setting.find(@active_record_id)
|
29
|
+
if @as_boolean
|
30
|
+
bool = !!new_value ? '1' : '0'
|
31
|
+
record.update_attribute :value, bool
|
32
|
+
else
|
33
|
+
record.update_attribute :value, new_value
|
34
|
+
end
|
35
|
+
when :memory
|
36
|
+
if @as_boolean
|
37
|
+
@value = !!new_value
|
38
|
+
else
|
39
|
+
@value = new_value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_value
|
45
|
+
cast_value(extract_data,@cast)
|
46
|
+
end
|
47
|
+
|
48
|
+
def formatted_klass_name
|
49
|
+
@klass.to_s.underscore.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_attributes!(args)
|
53
|
+
options = args.extract_options!
|
54
|
+
|
55
|
+
options.each do |key,value|
|
56
|
+
self.class.send :attr_accessor, key
|
57
|
+
self.send "#{key.to_s}=", value
|
58
|
+
end
|
59
|
+
|
60
|
+
@klass = formatted_klass_name
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def setup_active_record_object!
|
66
|
+
if @storage == :active_record
|
67
|
+
|
68
|
+
# find or create object
|
69
|
+
if @parent
|
70
|
+
parent_record = Setting.find_parent_by_namespace(@parent,@klass)
|
71
|
+
record = Setting.find_or_create_by_klass_and_name_and_parent_id(@klass,@name,parent_record.id)
|
72
|
+
else
|
73
|
+
record = Setting.find_or_create_by_klass_and_name(@klass,@name)
|
74
|
+
end
|
75
|
+
|
76
|
+
# setup value if needed
|
77
|
+
if record.value.nil?
|
78
|
+
if @as_boolean
|
79
|
+
bool_value = (!!@default ? '1' : '0')
|
80
|
+
record.update_attribute :value, bool_value
|
81
|
+
else
|
82
|
+
record.update_attribute :value, @default
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# hold acrive record id to fast search
|
87
|
+
self.class.send :attr_accessor, :active_record_id
|
88
|
+
@active_record_id = record.id
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def cast_value(value,cast_method = nil)
|
93
|
+
case cast_method
|
94
|
+
when Symbol
|
95
|
+
value.send(cast_method)
|
96
|
+
when Proc
|
97
|
+
cast_method.call(value)
|
98
|
+
else
|
99
|
+
value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def extract_data
|
104
|
+
result = case @storage
|
105
|
+
when :active_record
|
106
|
+
Setting.find(@active_record_id).value || @default
|
107
|
+
when :memory
|
108
|
+
@value || @default
|
109
|
+
end
|
110
|
+
|
111
|
+
if @as_boolean
|
112
|
+
result = result.to_i > 0 ? true : false
|
113
|
+
end
|
114
|
+
|
115
|
+
return result
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class SettingsLord::MetaSettingCollection
|
2
|
+
|
3
|
+
VALID_KEYS = [:default,:accepted_values,:cast,:as_frozen,:as_boolean,:storage]
|
4
|
+
BOOL_KEYS = [:as_frozen,:as_boolean]
|
5
|
+
|
6
|
+
BOOL_CLASSES = [TrueClass,FalseClass]
|
7
|
+
DEFAULT_VALUE_CLASSES = [Fixnum,String]
|
8
|
+
ACCEPTED_VALUES_CLASSES = [Array,Range,Regexp]
|
9
|
+
CAST_CLASSES = [Symbol,Proc]
|
10
|
+
SET_AND_GET_KEYS = [:klass,:namespace,:name,:default_value,:new_value]
|
11
|
+
|
12
|
+
POSSIBLE_STORAGES = [:active_record,:memory]
|
13
|
+
|
14
|
+
attr_reader :collection # contains all MetaOption objects
|
15
|
+
attr_reader :klasses_and_namespaces # contains classes and namespaces for fast look-up
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@collection = []
|
19
|
+
@klasses_and_namespaces = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def add(meta_option)
|
23
|
+
return false unless meta_option.is_a? SettingsLord::MetaSetting
|
24
|
+
|
25
|
+
remove_if_exists(meta_option)
|
26
|
+
fill_klasses_and_namespaces_table(meta_option)
|
27
|
+
self.collection << meta_option
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_klass?(klass_name)
|
31
|
+
self.klasses_and_namespaces.keys.include?(klass_name.to_sym)
|
32
|
+
end
|
33
|
+
|
34
|
+
def klass_has_namespace?(klass,namespace)
|
35
|
+
self.has_klass?(klass) and self.klasses_and_namespaces[klass].include?(namespace.to_sym)
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_by(reflection)
|
39
|
+
reflection.name = remove_set_tag(reflection.name)
|
40
|
+
|
41
|
+
if meta_option = find_by_reflection(reflection)
|
42
|
+
meta_option.update_value(reflection.new_value)
|
43
|
+
else
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_by(reflection)
|
49
|
+
find_by_reflection(reflection)
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_by_reflection(reflection)
|
53
|
+
# we can't search without klass and name
|
54
|
+
return nil if reflection.klass.blank? || reflection.name.blank?
|
55
|
+
|
56
|
+
# check klass
|
57
|
+
return nil unless has_klass?(reflection.klass)
|
58
|
+
|
59
|
+
# check namespace if needed
|
60
|
+
if reflection.reflect_like_namespace
|
61
|
+
return nil unless klass_has_namespace?(reflection.klass, reflection.parent)
|
62
|
+
end
|
63
|
+
|
64
|
+
if reflection.reflect_like_namespace
|
65
|
+
result = @collection.select do |entry|
|
66
|
+
entry.klass == reflection.klass and entry.name == reflection.name and entry.parent == reflection.parent
|
67
|
+
end
|
68
|
+
else
|
69
|
+
result = @collection.select do |entry|
|
70
|
+
entry.klass == reflection.klass and entry.name == reflection.name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
return result.first
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# transfer :method_name= to :method_name
|
80
|
+
def remove_set_tag(target)
|
81
|
+
target.to_s.gsub(/=$/,'').to_sym
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove_if_exists(target)
|
85
|
+
self.collection.each do |source|
|
86
|
+
self.collection.delete_if {|s| s.similar_to?(target)}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def fill_klasses_and_namespaces_table(option)
|
91
|
+
klass_name = option.formatted_klass_name
|
92
|
+
@klasses_and_namespaces[klass_name] ||= []
|
93
|
+
if option.respond_to? :parent
|
94
|
+
@klasses_and_namespaces[klass_name] << option.parent
|
95
|
+
@klasses_and_namespaces[klass_name].uniq!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class SettingsLord::Reflector
|
2
|
+
# reflector hold klass/namespace information and reflect on name
|
3
|
+
|
4
|
+
attr_accessor :name,:klass,:new_value,:reflect_like_namespace,:parent
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
setup_instance_variables!(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
# method missing will be called only when we return Reflector object
|
11
|
+
# this is default case when user attempt to nested option (option with namespace)
|
12
|
+
# for example:
|
13
|
+
#
|
14
|
+
# Option.view.default_path
|
15
|
+
#
|
16
|
+
# view method will return Reflecor object
|
17
|
+
#
|
18
|
+
# puts Option.view.class # => Optionator::Reflector
|
19
|
+
#
|
20
|
+
# default_path method will touch this method missing
|
21
|
+
#
|
22
|
+
# when we return Reflector object this object already get klass and namespace information
|
23
|
+
# we only need to setup called method name and some extra arguments
|
24
|
+
def method_missing(called_name,*args,&block)
|
25
|
+
@name = called_name.to_sym
|
26
|
+
@new_value = args.first
|
27
|
+
|
28
|
+
result = self.reflect
|
29
|
+
|
30
|
+
if result.is_a? SettingsLord::MetaSetting
|
31
|
+
return result.get_value
|
32
|
+
# for setters
|
33
|
+
elsif result.present?
|
34
|
+
return result
|
35
|
+
# for others
|
36
|
+
else
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# search for proper MetaOption in MetaOptionCollection and get/set needed value
|
42
|
+
def reflect()
|
43
|
+
should_search = @meta.has_klass?(@klass) or @meta.klass_has_namespace?(@klass,@name)
|
44
|
+
return nil unless should_search
|
45
|
+
|
46
|
+
if is_getter?
|
47
|
+
return create_sub_reflection if should_create_sub_reflection?
|
48
|
+
return @meta.get_by(self)
|
49
|
+
else
|
50
|
+
return @meta.set_by(self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def should_create_sub_reflection?
|
55
|
+
@reflect_like_namespace == false and @meta.klass_has_namespace?(@klass,@name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_sub_reflection
|
59
|
+
reflection = self.dup
|
60
|
+
reflection.reflect_like_namespace = true
|
61
|
+
reflection.parent = reflection.name.to_sym
|
62
|
+
reflection.name = nil
|
63
|
+
return reflection
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_getter?
|
67
|
+
not @name.to_s.end_with?('=')
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_value_called?
|
71
|
+
!!@name.to_s.match(/[a-zA-Z0-9]_default_value/)
|
72
|
+
end
|
73
|
+
|
74
|
+
def remove_default_value_tag_from_string
|
75
|
+
@name.to_s.gsub(/_default_value/,'').to_sym
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# @name/@parent/@klass should always be represented as Symbol
|
81
|
+
def setup_instance_variables!(args)
|
82
|
+
args = args.extract_options!
|
83
|
+
@name = args[:name].to_sym
|
84
|
+
@new_value = args[:new_value]
|
85
|
+
@klass = args[:klass]
|
86
|
+
@klass = args[:klass].model_name.underscore.to_sym if @klass.is_a? Class
|
87
|
+
@reflect_like_namespace = args[:reflect_like_namespace] || false
|
88
|
+
@meta = SettingsLord.meta_settings
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class SettingsLord::SettingCreator
|
2
|
+
|
3
|
+
# OptionCreator process new option creation
|
4
|
+
#
|
5
|
+
# OptionCreator check conditions and defend you from some stupid options :)
|
6
|
+
# for example:
|
7
|
+
# you try to create option which can hold only true/false values ( :as_boolean => true )
|
8
|
+
# but in :default flag you write some integer ( :default => 100 )
|
9
|
+
# OptionCreator handle this situation and warn you about this.
|
10
|
+
|
11
|
+
attr_accessor :parent
|
12
|
+
attr_accessor :klass
|
13
|
+
attr_accessor :meta
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@meta = SettingsLord.meta_settings.class # holds class for easy access to constants
|
17
|
+
@meta_collection = SettingsLord.meta_settings # holds meta collection
|
18
|
+
end
|
19
|
+
|
20
|
+
# each option creates via method missing
|
21
|
+
#
|
22
|
+
# define_options :test do |option|
|
23
|
+
# option.super_option :default => 10 # => method_missing will be called with 'super_action' as "name" argument
|
24
|
+
# end
|
25
|
+
def method_missing(name,*args,&block)
|
26
|
+
options = args.extract_options!
|
27
|
+
options.assert_valid_keys(@meta::VALID_KEYS)
|
28
|
+
options = check_and_maintain_options(options)
|
29
|
+
|
30
|
+
options[:klass] = @klass # option should know class
|
31
|
+
options[:parent] = @parent.name.to_sym if @parent
|
32
|
+
options[:name] = name
|
33
|
+
|
34
|
+
new_meta_option = SettingsLord::MetaSetting.new(options)
|
35
|
+
@meta_collection.add(new_meta_option)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def check_and_maintain_options(options)
|
41
|
+
check_bool_flags(options)
|
42
|
+
check_and_setup_storage(options)
|
43
|
+
check_accepted_values_flag(options)
|
44
|
+
check_and_setup_cast_flag(options)
|
45
|
+
check_default_value_flag(options)
|
46
|
+
return options
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_bool_flags(options)
|
50
|
+
@meta::BOOL_KEYS.each do |key|
|
51
|
+
if options[key] and not @meta::BOOL_CLASSES.include?(options[key].class)
|
52
|
+
raise Exception, "TrueClass or FalseClass expected but got instance of #{options[key].class} in #{key}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_and_setup_storage(options)
|
58
|
+
options[:storage] ||= :active_record
|
59
|
+
raise Exception, "Possible storages is -> #{@meta::POSSIBLE_STORAGES.inspect}" unless @meta::POSSIBLE_STORAGES.include?(options[:storage])
|
60
|
+
# force setup value if storage is virtual
|
61
|
+
options[:value] = options[:default] if options[:storage] == :virtual
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_accepted_values_flag(options)
|
65
|
+
if options[:accepted_values]
|
66
|
+
raise Exception,"Possible classes for :accepted_values flag is -> #{@meta::ACCEPTED_VALUES_CLASSES.inspect}, not a #{options[:accepted_values].class}" unless @meta::ACCEPTED_VALUES_CLASSES.include?(options[:accepted_values].class)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def check_and_setup_cast_flag(options)
|
71
|
+
if options[:cast]
|
72
|
+
raise Exception,"Possible classes for :cast flag is -> #{@meta::CAST_CLASSES}, not a #{options[:cast].class}" unless @meta::CAST_CLASSES.include?(options[:cast].class)
|
73
|
+
end
|
74
|
+
if options[:default] and options[:default].is_a? Fixnum and options[:cast].blank?
|
75
|
+
options[:cast] = :to_i
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def check_default_value_flag(options)
|
80
|
+
if options[:as_boolean] == true and options[:default].present? and not @meta::BOOL_CLASSES.include?(options[:default].class)
|
81
|
+
raise Exception, 'true or false should be as default value'
|
82
|
+
else
|
83
|
+
if not options[:as_boolean] and options[:default] and not @meta::DEFAULT_VALUE_CLASSES.include?(options[:default].class)
|
84
|
+
raise Exception, "Possible classes for :default flag is -> #{@meta::DEFAULT_VALUE_CLASSES.inspect}, not a #{options[:default].class}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.push File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
4
|
+
|
5
|
+
require 'settings_lord/base'
|
6
|
+
require 'settings_lord/setting_creator'
|
7
|
+
require 'settings_lord/reflector'
|
8
|
+
require 'settings_lord/active_record'
|
9
|
+
require 'settings_lord/meta_setting'
|
10
|
+
require 'settings_lord/meta_setting_collection'
|
11
|
+
require 'settings_lord/version'
|
12
|
+
|
13
|
+
require 'generators/settings_lord/settings_lord_generator'
|
14
|
+
|
15
|
+
# setup plugin
|
16
|
+
ActiveRecord::Base.send :extend, SettingsLord::ActiveRecord::ClassMethods
|
17
|
+
SettingsLord.setup_plugin!
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require './lib/settings_lord/version.rb'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "settings_lord"
|
6
|
+
s.version = SettingsLord::Version::STRING
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["pechrorin_andrey"]
|
9
|
+
s.email = ["pechorin.andrey@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/pechorin/settings_lord"
|
11
|
+
s.summary = %q{Best way to manage your site settings}
|
12
|
+
s.description = %q{Best way to manage your site settings}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SettingsLordTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
Setting.delete_all
|
7
|
+
SettingsLord.meta_settings.instance_variable_set :@collection, []
|
8
|
+
end
|
9
|
+
|
10
|
+
test "should accept only boolean values" do
|
11
|
+
assert_nothing_raised do
|
12
|
+
Setting.settings do
|
13
|
+
option :as_frozen => true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_raise Exception do
|
18
|
+
Setting.settings do
|
19
|
+
option :as_frozen => 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
test "should accept only known storage" do
|
25
|
+
assert_nothing_raised do
|
26
|
+
Setting.settings do
|
27
|
+
option :storage => :active_record
|
28
|
+
option_2 :storage => :memory
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_raise Exception do
|
33
|
+
Setting.settings do
|
34
|
+
option :storage => :javadoc
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
test "should accept only symbols and proc for :cast" do
|
40
|
+
assert_raise Exception do
|
41
|
+
Setting.settings do
|
42
|
+
option :cast => [1,2,3]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
assert_nothing_raised do
|
47
|
+
Setting.settings do
|
48
|
+
option :cast => :to_f
|
49
|
+
option_2 :cast => lambda {}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
test "bool flag" do
|
55
|
+
assert_raise Exception do
|
56
|
+
Setting.settings do
|
57
|
+
option :as_boolean => true, :default => 2
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
assert_nothing_raised do
|
62
|
+
Setting.settings do
|
63
|
+
option :as_boolean => true, :default => false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
test "default value flag" do
|
69
|
+
assert_raise Exception do
|
70
|
+
Setting.settings do
|
71
|
+
option :default => [1,2,3]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
assert_nothing_raised do
|
76
|
+
Setting.settings do
|
77
|
+
option_1 :default => "Hello"
|
78
|
+
option_2 :default => 100
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
test "should not duplicate virtual meta options" do
|
84
|
+
collection = SettingsLord.meta_settings.collection
|
85
|
+
start_size = collection.size
|
86
|
+
|
87
|
+
Setting.settings do
|
88
|
+
name
|
89
|
+
end
|
90
|
+
assert collection.size == start_size + 1
|
91
|
+
|
92
|
+
Setting.settings do
|
93
|
+
anothet_name
|
94
|
+
end
|
95
|
+
assert collection.size == start_size + 2
|
96
|
+
|
97
|
+
Setting.settings do
|
98
|
+
name
|
99
|
+
end
|
100
|
+
assert collection.size == start_size + 2
|
101
|
+
end
|
102
|
+
|
103
|
+
test "should get right value" do
|
104
|
+
|
105
|
+
assert_raise NoMethodError do
|
106
|
+
Setting.number
|
107
|
+
end
|
108
|
+
|
109
|
+
Setting.settings do
|
110
|
+
number :default => 10
|
111
|
+
end
|
112
|
+
|
113
|
+
assert Setting.number == 10
|
114
|
+
end
|
115
|
+
|
116
|
+
test "automatic casting" do
|
117
|
+
Setting.settings do
|
118
|
+
a :default => 10
|
119
|
+
b :default => '10'
|
120
|
+
end
|
121
|
+
|
122
|
+
assert Setting.a == 10
|
123
|
+
assert Setting.b == '10'
|
124
|
+
end
|
125
|
+
|
126
|
+
test "casting" do
|
127
|
+
Setting.settings do
|
128
|
+
number :default => 10, :cast => :to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
assert Setting.number == "10"
|
132
|
+
|
133
|
+
Setting.settings do
|
134
|
+
number :default => 10, :cast => lambda {|value| value.to_s << "!!!"}
|
135
|
+
end
|
136
|
+
|
137
|
+
assert Setting.number == "10!!!"
|
138
|
+
end
|
139
|
+
|
140
|
+
test "set option" do
|
141
|
+
Setting.settings do
|
142
|
+
number :default => 10
|
143
|
+
end
|
144
|
+
|
145
|
+
Setting.number = 20
|
146
|
+
assert Setting.number == 20
|
147
|
+
end
|
148
|
+
|
149
|
+
test "frozen options" do
|
150
|
+
Setting.settings do
|
151
|
+
number :default => 10, :as_frozen => true
|
152
|
+
end
|
153
|
+
|
154
|
+
assert_raise NoMethodError do
|
155
|
+
Setting.number = 20
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
test 'in-memory options' do
|
160
|
+
Setting.settings do
|
161
|
+
in_memory_number :default => 10, :storage => :memory
|
162
|
+
end
|
163
|
+
|
164
|
+
assert Setting.find_by_name('in_memory_number') == nil && Setting.in_memory_number == 10
|
165
|
+
|
166
|
+
Setting.in_memory_number = 30
|
167
|
+
assert Setting.in_memory_number == 30
|
168
|
+
end
|
169
|
+
|
170
|
+
test 'as_boolean options' do
|
171
|
+
Setting.settings do
|
172
|
+
bool_value :default => true, :as_boolean => true
|
173
|
+
end
|
174
|
+
|
175
|
+
assert Setting.find_by_name('bool_value').value == '1' && Setting.bool_value.is_a?(TrueClass)
|
176
|
+
|
177
|
+
Setting.bool_value = false
|
178
|
+
assert Setting.find_by_name('bool_value').value == '0' && Setting.bool_value.is_a?(FalseClass)
|
179
|
+
end
|
180
|
+
|
181
|
+
test 'accepted_values options' do
|
182
|
+
Setting.settings do
|
183
|
+
some_super_option :default => 0, :accepted_values => 0..3
|
184
|
+
string_super_option :default => "en", :accepted_values => ['ru','en','by']
|
185
|
+
end
|
186
|
+
|
187
|
+
assert_raise Exception do
|
188
|
+
Setting.some_super_option = 4
|
189
|
+
end
|
190
|
+
assert (Setting.some_super_option = 1) && (Setting.some_super_option == 1)
|
191
|
+
|
192
|
+
assert_raise Exception do
|
193
|
+
Setting.string_super_option = 'us'
|
194
|
+
end
|
195
|
+
assert Setting.string_super_option = 'ru'
|
196
|
+
end
|
197
|
+
|
198
|
+
test "regexp accepted values" do
|
199
|
+
Setting.settings do
|
200
|
+
some_option :default => 0, :accepted_values => /abc/
|
201
|
+
end
|
202
|
+
|
203
|
+
assert Setting.some_option = 'abc'
|
204
|
+
assert_raise Exception do
|
205
|
+
Setting.some_option = 'abd'
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
test 'namespaces get' do
|
210
|
+
Setting.settings :view do
|
211
|
+
nested_number :default => 10
|
212
|
+
end
|
213
|
+
|
214
|
+
assert Setting.view.is_a? SettingsLord::Reflector
|
215
|
+
assert Setting.view.nested_number == 10
|
216
|
+
end
|
217
|
+
|
218
|
+
test 'namespace set' do
|
219
|
+
Setting.settings :view do
|
220
|
+
nested_number :default => 10
|
221
|
+
end
|
222
|
+
|
223
|
+
Setting.view.nested_number = 20
|
224
|
+
assert Setting.view.nested_number == 20
|
225
|
+
end
|
226
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:", :database => 'settings_test')
|
7
|
+
|
8
|
+
ActiveRecord::Schema.define(:version => 1) do
|
9
|
+
create_table :settings, :force => true do |t|
|
10
|
+
t.string :name
|
11
|
+
t.text :value
|
12
|
+
t.string :klass
|
13
|
+
t.integer :parent_id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Setting < ActiveRecord::Base
|
18
|
+
end
|
19
|
+
|
20
|
+
require File.join(File.dirname(__FILE__),'..','lib','settings_lord.rb')
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: settings_lord
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- pechrorin_andrey
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-30 00:00:00 +04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Best way to manage your site settings
|
18
|
+
email:
|
19
|
+
- pechorin.andrey@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- MIT-LICENSE
|
28
|
+
- README.md
|
29
|
+
- Rakefile
|
30
|
+
- lib/generators/settings_lord/settings_lord_generator.rb
|
31
|
+
- lib/generators/settings_lord/templates/migration.rb
|
32
|
+
- lib/generators/settings_lord/templates/setting.rb
|
33
|
+
- lib/settings_lord.rb
|
34
|
+
- lib/settings_lord/active_record.rb
|
35
|
+
- lib/settings_lord/base.rb
|
36
|
+
- lib/settings_lord/meta_setting.rb
|
37
|
+
- lib/settings_lord/meta_setting_collection.rb
|
38
|
+
- lib/settings_lord/reflector.rb
|
39
|
+
- lib/settings_lord/setting_creator.rb
|
40
|
+
- lib/settings_lord/version.rb
|
41
|
+
- settings_lord.gemspec
|
42
|
+
- test/settings_lord_test.rb
|
43
|
+
- test/test_helper.rb
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: https://github.com/pechorin/settings_lord
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.6.2
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Best way to manage your site settings
|
72
|
+
test_files:
|
73
|
+
- test/settings_lord_test.rb
|
74
|
+
- test/test_helper.rb
|