has_setting 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README +112 -0
- data/Rakefile +20 -0
- data/VERSION.yml +4 -0
- data/has_setting.gemspec +29 -0
- data/help/001_create_settings.rb +15 -0
- data/lib/has_setting/ar_extensions.rb +70 -0
- data/lib/has_setting/formatters.rb +140 -0
- data/lib/has_setting/setting.rb +7 -0
- data/lib/has_setting/version.rb +3 -0
- data/lib/has_setting.rb +20 -0
- data/test/bar.rb +4 -0
- data/test/baz.rb +2 -0
- data/test/foo.rb +8 -0
- data/test/test_helper.rb +36 -0
- data/test/unit/formatters_test.rb +183 -0
- data/test/unit/has_setting_test.rb +148 -0
- metadata +73 -0
data/.gitignore
ADDED
data/README
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
==What is it?
|
2
|
+
has_setting is a simple extension that enables ActiveRecord models to
|
3
|
+
store settings in a separate settings table as key/value pairs where the key and value are stored as Strings.
|
4
|
+
|
5
|
+
==History
|
6
|
+
* 0.4.3:
|
7
|
+
* Changed behaviour of :boolean formatters: Now they treat '0', 0, false, nil and '' as false, everything else as true
|
8
|
+
This is not the same behaviour as ruby (treating only nil and false as false) but should help with input from web forms (i.e. checkboxes)
|
9
|
+
If you want strict ruby boolean behaviour, then use :strict_boolean as :type
|
10
|
+
* 0.4.2:
|
11
|
+
* bug fixes for boolean types default values
|
12
|
+
* 0.3.10:
|
13
|
+
* added boolean and booleans formatters
|
14
|
+
* 0.3.9:
|
15
|
+
* added type :strings, :floats, :ints. They store the contents of an array as a comma separated string.
|
16
|
+
* 0.3.8:
|
17
|
+
* added dependent destroy option. no more zombie settings lingering around.
|
18
|
+
* 0.3.7:
|
19
|
+
* Gem is now built using jeweler... after messing around and bumping versions and getting
|
20
|
+
strange errors, this is 'it works' feeling coming back
|
21
|
+
* 0.3.4:
|
22
|
+
* Added custom formatter support. no new formatters though...
|
23
|
+
* 0.3.1:
|
24
|
+
* Bug Fixed: has_many(:settings) is not added to ActiveRecord::Base but only to the classes with has_setting
|
25
|
+
* Bug Fixed: options are not shared between classes
|
26
|
+
* Again changed the way settings are saved. Save is now done on parent.save with an after_save callback. (like this the settings are treated as if they were attributes of the owner)
|
27
|
+
* 0.2.x:
|
28
|
+
* Added :default option
|
29
|
+
* changed way settings are saved so that unsaved parents can have settings too
|
30
|
+
* changed nameing scheme of setting names (incompatible with versions prior 0.2.x but since nobody uses the gem i dont care :-))
|
31
|
+
* 0.1.x: First Version
|
32
|
+
|
33
|
+
|
34
|
+
==Installation
|
35
|
+
sudo gem install simplificator-has_setting
|
36
|
+
|
37
|
+
==Setup
|
38
|
+
* Add a migration that looks more or less like the one in <em>help/001_create_settings.rb</em>
|
39
|
+
* Make sure the gem is loaded when your application starts
|
40
|
+
|
41
|
+
==Config
|
42
|
+
The model you want to hold settings (i.e. User, Profile, ...):
|
43
|
+
<tt>has_setting(:name_of_the_setting)</tt>
|
44
|
+
This will create the following methods for you on the owner class:
|
45
|
+
* <tt>name_of_the_setting=(value)</tt> a standard setter
|
46
|
+
* <tt>name_of_the_setting()</tt> a standard getter (the getter method takes an optional hash to override some options, possible values are the same as the options in has_setting())
|
47
|
+
|
48
|
+
<tt>has_setting(name, options)</tt> takes an optional hash of options. Following options are supported:
|
49
|
+
<em>:type</em> allows you to convert the value:
|
50
|
+
* <em>:string</em> (default) Uses the StringFormatter to convert from/to String (actually this formatter just leaves the value as it is)
|
51
|
+
* <em>:int</em> Uses the IntFormatter to convert from/to int values.
|
52
|
+
* <em>:boolean</em> Uses the BooleanFormatter to convert from/to boolean values; treating 0, '0', false, '' and nil as false.
|
53
|
+
* <em>:strict_boolean</em> Uses the StrictBooleanFormatter to convert from/to boolean values; treating false, and nil as false.
|
54
|
+
* <em>:float</em> Uses the FloatFormatter to convert from/to float values.
|
55
|
+
* <em>:ints</em> Uses the IntsFormatter to convert from/to int[]
|
56
|
+
* <em>:floats</em> Uses the FloatsFormatter to convert from/to float[]
|
57
|
+
* <em>:strings</em> Uses the StringsFormatter to convert from/to string[]
|
58
|
+
* <em>:booleans</em> Uses the BooleansFormatter to convert from/to boolean[]
|
59
|
+
* <em>:strict_booleans</em> Uses the BooleansFormatter to convert from/to boolean[]
|
60
|
+
<em>:default</em> allows you to specify a default value that will be returned if the setting does not exist (i.e. has never been written). Note that the default value is _ignored_ if the setting exists, no matter what the value of the setting is. The default value is returned as is, no type conversion takes place.
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
==How it works
|
65
|
+
A polymorphic parent-child relation is created between Setting and the parent/owning class.
|
66
|
+
Getters/setters are added through meta-programming-magic. If the setter is invoked on a unsafed parent then the setting is not saved until the parent is saved, else setting is saved upon creation (i.e. first time the setter is called) / change (subsequent calls).
|
67
|
+
The getters/setters can be used in standard AR validations, Rails mass assignments/form helpers and so on.
|
68
|
+
|
69
|
+
==Gotchas
|
70
|
+
* Values are stored as Strings in the DB. Values are converted with one of the formatters (depending on selected :type). If you try to store an unsupported type or anything other than the type you selected there might be an exception (i.e. if you try to store "foobar" as an :type => :int)
|
71
|
+
* Currently there are no length validations on the 'name' and 'value' column of Setting. Take care not to store values to big. Especially when using the array formatters (:floats, :ints, :strings)
|
72
|
+
|
73
|
+
|
74
|
+
==Example
|
75
|
+
<code>
|
76
|
+
class Foo < ActiveRecord::Base
|
77
|
+
has_setting(:string_setting)
|
78
|
+
has_setting(:another_string_setting, :type => :string)
|
79
|
+
has_setting(:int_setting, :type => :int)
|
80
|
+
has_setting(:float_setting, :type => :float, :default => 3.3)
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
foo = Foo.create
|
85
|
+
|
86
|
+
foo.string_setting
|
87
|
+
=> nil
|
88
|
+
foo.string_setting= 'a string'
|
89
|
+
foo.string_setting
|
90
|
+
=> 'a string'
|
91
|
+
|
92
|
+
foo.int_setting = 123
|
93
|
+
foo.int_setting
|
94
|
+
=> 123
|
95
|
+
foo.int_setting = '123'
|
96
|
+
foo.int_setting
|
97
|
+
=> 123
|
98
|
+
|
99
|
+
foo.float_setting
|
100
|
+
=> 3.3
|
101
|
+
foo.float_setting = nil
|
102
|
+
foo.float_setting
|
103
|
+
=> nil
|
104
|
+
|
105
|
+
</code>
|
106
|
+
|
107
|
+
|
108
|
+
==Todo
|
109
|
+
has_setting should stay as simple as possible... still some ideas are around:
|
110
|
+
* Custom formatter (to convert arbitrary objects, i.e. Date/Time/DateTime...)
|
111
|
+
* Add validation options
|
112
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rake/testtask"
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
t.verbose = false
|
9
|
+
end
|
10
|
+
|
11
|
+
# require 'rake/rdoctask'
|
12
|
+
# Rake::RDocTask.new do |rdoc|
|
13
|
+
# rdoc.rdoc_dir = 'rdoc'
|
14
|
+
# rdoc.title = 'has_setting'
|
15
|
+
# rdoc.options << '--line-numbers' << '--inline-source'
|
16
|
+
# rdoc.rdoc_files.include('README*')
|
17
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
18
|
+
# end
|
19
|
+
|
20
|
+
task :default => :test
|
data/VERSION.yml
ADDED
data/has_setting.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/has_setting/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Simplificator GmbH", "Nico Ritsche"]
|
7
|
+
gem.email = ['info@simplificator.com', "ncrdevmail@gmail.com"]
|
8
|
+
gem.description = %q{Stores settings as key/value pairs in a settings table and provides accessors for them on the owning object}
|
9
|
+
gem.summary = %q{Simple setting extension to AR}
|
10
|
+
gem.homepage = "http://github.com/ncri/has_setting"
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "has_setting"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = HasSetting::VERSION
|
18
|
+
gem.has_rdoc = true
|
19
|
+
gem.rdoc_options = ["--charset=UTF-8"]
|
20
|
+
gem.test_files = [
|
21
|
+
"test/bar.rb",
|
22
|
+
"test/baz.rb",
|
23
|
+
"test/foo.rb",
|
24
|
+
"test/test_helper.rb",
|
25
|
+
"test/unit/formatters_test.rb",
|
26
|
+
"test/unit/has_setting_test.rb"
|
27
|
+
]
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateSettings < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :settings do |t|
|
4
|
+
t.string(:owner_type, :null => false)
|
5
|
+
t.integer(:owner_id, :null => false)
|
6
|
+
t.string(:name, :null => false, :length => 64)
|
7
|
+
t.string(:value, :null => true, :length => 255)
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table(:settings)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module HasSetting
|
2
|
+
|
3
|
+
module InstanceMethods
|
4
|
+
def write_setting(name, value)
|
5
|
+
# find an existing setting or build a new one
|
6
|
+
setting = self.settings.detect() {|item| item.name == name }
|
7
|
+
setting = self.settings.build(:name => name) if setting.blank?
|
8
|
+
setting.value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def read_setting(name)
|
12
|
+
# use detect instead of SQL find. like this the 'cached' has_many-collection is inited
|
13
|
+
# only once
|
14
|
+
self.settings.detect() {|item| item.name == name }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
# Setup of the getter/setter
|
20
|
+
def has_setting(name, options = {})
|
21
|
+
name = name.to_s
|
22
|
+
raise ArgumentError.new('Setting name must not be blank') if name.blank?
|
23
|
+
|
24
|
+
self.class_eval do
|
25
|
+
unless @has_setting_options # define only once
|
26
|
+
# AR association to settings
|
27
|
+
has_many( :settings, :as => :owner, :class_name => 'HasSetting::Setting',
|
28
|
+
:foreign_key => :owner_id, :dependent => :destroy)
|
29
|
+
after_save(:save_has_setting_association)
|
30
|
+
@has_setting_options = {}
|
31
|
+
def self.has_setting_options
|
32
|
+
@has_setting_options
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
# Callback to save settings
|
37
|
+
def save_has_setting_association
|
38
|
+
self.settings.each do |setting|
|
39
|
+
setting.save if setting.changed?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
raise ArgumentError.new("Setting #{name }is already defined on #{self.name}") if self.has_setting_options.has_key?(name)
|
47
|
+
|
48
|
+
# default options
|
49
|
+
options[:type] ||= :string # treat as string
|
50
|
+
# default could be false, thats why we use has_key?
|
51
|
+
options[:default] = options.has_key?(:default) ? options[:default] : nil # no default
|
52
|
+
self.has_setting_options[name] = options
|
53
|
+
|
54
|
+
# setter
|
55
|
+
define_method("#{name}=".intern) do |value|
|
56
|
+
formatter = HasSetting::Formatters.for_type(options[:type])
|
57
|
+
write_setting(name, formatter.to_s(value))
|
58
|
+
end
|
59
|
+
|
60
|
+
# getter
|
61
|
+
define_method(name) do |*args|
|
62
|
+
setting = read_setting(name)
|
63
|
+
options = args.first || self.class.has_setting_options[name]
|
64
|
+
return options[:default] if setting.nil?
|
65
|
+
formatter = Formatters.for_type(options[:type])
|
66
|
+
formatter.to_type(setting.value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module HasSetting
|
2
|
+
module Formatters
|
3
|
+
@@formatters = {}
|
4
|
+
#
|
5
|
+
# register a formatter:
|
6
|
+
# * type: a Symbol that is used to identify this formatter in the has_setting options hash via the :type option
|
7
|
+
# * formatter: the formatter, an object which supports to_type and to_s methods
|
8
|
+
def self.register_formatter(type, formatter)
|
9
|
+
if !formatter.respond_to?(:to_s) || !formatter.respond_to?(:to_type)
|
10
|
+
raise ArgumentError.new('Formatter does not support to_s/to_type')
|
11
|
+
end
|
12
|
+
@@formatters[type] = formatter
|
13
|
+
end
|
14
|
+
|
15
|
+
# Lookup a Formatter by type symbol
|
16
|
+
# raises ArgumentError if the formatter is not found
|
17
|
+
def self.for_type(type)
|
18
|
+
formatter = @@formatters[type]
|
19
|
+
raise ArgumentError.new("Can not find a formatter for #{type}") unless formatter
|
20
|
+
formatter
|
21
|
+
end
|
22
|
+
|
23
|
+
# Helper class which handles nil values
|
24
|
+
class NilSafeFormatter
|
25
|
+
# Converts the String from DB to the specified type
|
26
|
+
# Nil is returned for nil values
|
27
|
+
def to_type(value)
|
28
|
+
safe_to_type(value) unless value == nil
|
29
|
+
end
|
30
|
+
# Converts the value to String for storing in DB
|
31
|
+
# Nil is returned for nil values
|
32
|
+
def to_s(value)
|
33
|
+
safe_to_s(value) unless value == nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Formatter for Strings
|
38
|
+
class StringFormatter < NilSafeFormatter
|
39
|
+
def safe_to_type(value)
|
40
|
+
value
|
41
|
+
end
|
42
|
+
def safe_to_s(value)
|
43
|
+
value.to_s
|
44
|
+
end
|
45
|
+
end
|
46
|
+
# Convert a Boolean to String and Back
|
47
|
+
# nil, '0', false, 0 and '' are considered __false__, everything else is __true__
|
48
|
+
# This is not like ruby where only nil and false are considered __false__
|
49
|
+
class BooleanFormatter < NilSafeFormatter
|
50
|
+
def safe_to_type(value)
|
51
|
+
value == '1'
|
52
|
+
end
|
53
|
+
def safe_to_s(value)
|
54
|
+
value && value != '0' && value != 0 && value != '' ? '1' : '0'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
class BooleansFormatter < NilSafeFormatter
|
58
|
+
def safe_to_type(value)
|
59
|
+
value.split(',').map() {|item| Formatters.for_type(:boolean).to_type(item)}
|
60
|
+
end
|
61
|
+
def safe_to_s(value)
|
62
|
+
Array(value).map() {|item| Formatters.for_type(:boolean).to_s(item)}.join(',')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class StrictBooleanFormatter < NilSafeFormatter
|
67
|
+
def safe_to_type(value)
|
68
|
+
value == '1'
|
69
|
+
end
|
70
|
+
def safe_to_s(value)
|
71
|
+
value ? '1' : '0'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
class StrictBooleansFormatter < NilSafeFormatter
|
75
|
+
def safe_to_type(value)
|
76
|
+
value.split(',').map() {|item| Formatters.for_type(:strict_boolean).to_type(item)}
|
77
|
+
end
|
78
|
+
def safe_to_s(value)
|
79
|
+
Array(value).map() {|item| Formatters.for_type(:strict_boolean).to_s(item)}.join(',')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class IntsFormatter < NilSafeFormatter
|
84
|
+
def safe_to_type(value)
|
85
|
+
value.split(',').map() {|item| Formatters.for_type(:int).to_type(item)}
|
86
|
+
end
|
87
|
+
def safe_to_s(value)
|
88
|
+
Array(value).map() {|item| Formatters.for_type(:int).to_s(item)}.join(',')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class FloatsFormatter < NilSafeFormatter
|
93
|
+
def safe_to_type(value)
|
94
|
+
value.split(',').map() {|item| Formatters.for_type(:float).to_type(item)}
|
95
|
+
end
|
96
|
+
def safe_to_s(value)
|
97
|
+
Array(value).map() {|item| Formatters.for_type(:float).to_s(item)}.join(',')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class StringsFormatter < NilSafeFormatter
|
102
|
+
def safe_to_type(value)
|
103
|
+
# Ruby does not know "negative look before". Or i dont know how to do it in ruby. Thus
|
104
|
+
# i ended up using some reverse calls... ugly. Anyone out there eager to help me out?
|
105
|
+
value.reverse.split(/,(?!\\)/).map() {|item| item.reverse.gsub('\,', ',')}.reverse
|
106
|
+
end
|
107
|
+
def safe_to_s(value)
|
108
|
+
# Escape the separator character ',' with a backslash
|
109
|
+
Array(value).map() {|item| item.gsub(',', '\,')}.join(',')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Formatter for ints
|
115
|
+
# Throws ArgumentError if value can not be converted
|
116
|
+
class IntFormatter < NilSafeFormatter
|
117
|
+
# Integer() does not treat "2.6" the same as 2.6
|
118
|
+
# while 2.6 is a valid Intger() (-> 2), "2.6" is not.
|
119
|
+
# Note that "2" is a valid argument for Integer() and that "".to_i is valid
|
120
|
+
# while Integer('') is not...
|
121
|
+
# Circumvent this by first convert with Float() so everything obeys to the same rules
|
122
|
+
def safe_to_type(value)
|
123
|
+
Integer(Float(value))
|
124
|
+
end
|
125
|
+
def safe_to_s(value)
|
126
|
+
Integer(Float(value)).to_s
|
127
|
+
end
|
128
|
+
end
|
129
|
+
# Formatter for float values
|
130
|
+
# Throws ArgumentError if value can not be converted
|
131
|
+
class FloatFormatter < NilSafeFormatter
|
132
|
+
def safe_to_type(value)
|
133
|
+
Float(value)
|
134
|
+
end
|
135
|
+
def safe_to_s(value)
|
136
|
+
Float(value).to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/has_setting.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Intialize the gem by including some extension to ActiveRecord::Base
|
2
|
+
require File.dirname(__FILE__) + '/has_setting/ar_extensions'
|
3
|
+
require File.dirname(__FILE__) + '/has_setting/formatters'
|
4
|
+
require File.dirname(__FILE__) + '/has_setting/setting'
|
5
|
+
ActiveRecord::Base.class_eval do
|
6
|
+
include(HasSetting::InstanceMethods)
|
7
|
+
extend(HasSetting::ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
HasSetting::Formatters.register_formatter(:string, HasSetting::Formatters::StringFormatter.new)
|
12
|
+
HasSetting::Formatters.register_formatter(:strings, HasSetting::Formatters::StringsFormatter.new)
|
13
|
+
HasSetting::Formatters.register_formatter(:float, HasSetting::Formatters::FloatFormatter.new)
|
14
|
+
HasSetting::Formatters.register_formatter(:floats, HasSetting::Formatters::FloatsFormatter.new)
|
15
|
+
HasSetting::Formatters.register_formatter(:int, HasSetting::Formatters::IntFormatter.new)
|
16
|
+
HasSetting::Formatters.register_formatter(:ints, HasSetting::Formatters::IntsFormatter.new)
|
17
|
+
HasSetting::Formatters.register_formatter(:boolean, HasSetting::Formatters::BooleanFormatter.new)
|
18
|
+
HasSetting::Formatters.register_formatter(:booleans, HasSetting::Formatters::BooleansFormatter.new)
|
19
|
+
HasSetting::Formatters.register_formatter(:strict_boolean, HasSetting::Formatters::StrictBooleanFormatter.new)
|
20
|
+
HasSetting::Formatters.register_formatter(:strict_booleans, HasSetting::Formatters::StrictBooleansFormatter.new)
|
data/test/bar.rb
ADDED
data/test/baz.rb
ADDED
data/test/foo.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/../lib/has_setting'
|
6
|
+
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
:adapter => 'sqlite3',
|
10
|
+
:database => 'test.sqlite3',
|
11
|
+
:timeout => 5000
|
12
|
+
)
|
13
|
+
|
14
|
+
ActiveRecord::Base.connection.drop_table(:settings) rescue ActiveRecord::StatementInvalid
|
15
|
+
[:foos, :bars, :bazs].each do |table|
|
16
|
+
ActiveRecord::Base.connection.drop_table(table) rescue ActiveRecord::StatementInvalid
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
ActiveRecord::Base.connection.create_table(:settings) do |table|
|
22
|
+
table.string(:value, :limit => 255)
|
23
|
+
table.string(:name, :limit => 64, :null => false)
|
24
|
+
table.string(:owner_type, :limit => 255, :null => false)
|
25
|
+
table.integer(:owner_id, :null => false)
|
26
|
+
end
|
27
|
+
[:foos, :bars, :bazs].each do |table|
|
28
|
+
ActiveRecord::Base.connection.create_table(table) do |t|
|
29
|
+
end
|
30
|
+
end
|
31
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
32
|
+
ActiveRecord::Base.logger.level = Logger::DEBUG # change to DEBUG if you want to see something :-)
|
33
|
+
|
34
|
+
require File.join(File.dirname(__FILE__), 'foo')
|
35
|
+
require File.join(File.dirname(__FILE__), 'bar')
|
36
|
+
require File.join(File.dirname(__FILE__), 'baz')
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
include HasSetting
|
3
|
+
class FormattersTest < Test::Unit::TestCase
|
4
|
+
def test_for_type
|
5
|
+
[:string, :float, :floats, :int, :ints, :strings, :boolean, :booleans, :strict_boolean, :strict_booleans].each do |symbol|
|
6
|
+
assert(Formatters.for_type(symbol), "No formatter for #{symbol}")
|
7
|
+
if symbol == :strict_boolean
|
8
|
+
assert_equal(Formatters.for_type(symbol).class.to_s, "HasSetting::Formatters::StrictBooleanFormatter")
|
9
|
+
elsif symbol == :strict_booleans
|
10
|
+
assert_equal(Formatters.for_type(symbol).class.to_s, "HasSetting::Formatters::StrictBooleansFormatter")
|
11
|
+
else
|
12
|
+
assert_equal(Formatters.for_type(symbol).class.to_s, "HasSetting::Formatters::#{symbol.to_s.capitalize}Formatter")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
assert_raises(ArgumentError) do
|
16
|
+
Formatters.for_type(:rarararararara_i_do_not_exist)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_strings_formatter
|
21
|
+
f = Formatters::StringsFormatter.new
|
22
|
+
assert_equal(nil, f.to_s(nil))
|
23
|
+
assert_equal('bla', f.to_s('bla'))
|
24
|
+
assert_equal('bla', f.to_s(['bla']))
|
25
|
+
assert_equal('bla,bli', f.to_s(['bla', 'bli']))
|
26
|
+
assert_equal('\,schni\,schna\,,bli,\,bla', f.to_s([',schni,schna,', 'bli', ',bla']))
|
27
|
+
assert_equal('\,\,\,\,,\,\,\,,\,\,,\,', f.to_s([',,,,', ',,,', ',,', ',']))
|
28
|
+
|
29
|
+
assert_equal(nil, f.to_type(nil))
|
30
|
+
assert_equal([], f.to_type(''))
|
31
|
+
assert_equal(['bli'], f.to_type('bli'))
|
32
|
+
assert_equal(['bli','', 'bla'], f.to_type('bli,,bla'))
|
33
|
+
assert_equal([',schni,schna,', 'bli', ',bla'], f.to_type('\,schni\,schna\,,bli,\,bla'))
|
34
|
+
assert_equal([',,,,', ',,,', ',,', ','], f.to_type('\,\,\,\,,\,\,\,,\,\,,\,'))
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def test_string_formatter()
|
39
|
+
f = Formatters::StringFormatter.new
|
40
|
+
assert_equal('', f.to_s(''))
|
41
|
+
assert_equal('a', f.to_s('a'))
|
42
|
+
assert_equal('', f.to_type(''))
|
43
|
+
assert_equal('a', f.to_type('a'))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_boolean_formatter
|
47
|
+
f = Formatters::BooleanFormatter.new
|
48
|
+
assert_equal('0', f.to_s(''))
|
49
|
+
assert_equal('1', f.to_s(true))
|
50
|
+
assert_equal('0', f.to_s(false))
|
51
|
+
assert_equal('0', f.to_s('0'))
|
52
|
+
assert_equal('0', f.to_s(0))
|
53
|
+
assert_equal('0', f.to_s(''))
|
54
|
+
assert_equal(nil, f.to_s(nil))
|
55
|
+
|
56
|
+
assert_equal(true, f.to_type('1'))
|
57
|
+
assert_equal(false, f.to_type('0'))
|
58
|
+
assert_equal(nil, f.to_type(nil))
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_strict_boolean_formatter
|
62
|
+
f = Formatters::StrictBooleanFormatter.new
|
63
|
+
assert_equal('1', f.to_s(''))
|
64
|
+
assert_equal('1', f.to_s(true))
|
65
|
+
assert_equal('0', f.to_s(false))
|
66
|
+
assert_equal('1', f.to_s('0'))
|
67
|
+
assert_equal(nil, f.to_s(nil))
|
68
|
+
|
69
|
+
assert_equal(true, f.to_type('1'))
|
70
|
+
assert_equal(false, f.to_type('0'))
|
71
|
+
assert_equal(nil, f.to_type(nil))
|
72
|
+
end
|
73
|
+
def test_int_formatter()
|
74
|
+
f = Formatters::IntFormatter.new
|
75
|
+
assert_raises(ArgumentError) do
|
76
|
+
f.to_s('')
|
77
|
+
end
|
78
|
+
assert_raises(ArgumentError) do
|
79
|
+
f.to_s('asas')
|
80
|
+
end
|
81
|
+
assert_nil(f.to_s(nil))
|
82
|
+
assert_equal('2', f.to_s(2.6))
|
83
|
+
assert_equal('2', f.to_s(2))
|
84
|
+
|
85
|
+
assert_raises(ArgumentError) do
|
86
|
+
f.to_type('')
|
87
|
+
end
|
88
|
+
assert_raises(ArgumentError) do
|
89
|
+
f.to_type('asas')
|
90
|
+
end
|
91
|
+
assert_nil(f.to_type(nil))
|
92
|
+
assert_equal(2, f.to_type('2'))
|
93
|
+
assert_equal(2, f.to_type('2.6'))
|
94
|
+
assert_equal(2, f.to_type(' 2.6 '))
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_float_formatter()
|
98
|
+
f = Formatters::FloatFormatter.new
|
99
|
+
assert_raises(ArgumentError) do
|
100
|
+
f.to_s('')
|
101
|
+
end
|
102
|
+
assert_raises(ArgumentError) do
|
103
|
+
f.to_s('asas')
|
104
|
+
end
|
105
|
+
assert_nil(f.to_s(nil))
|
106
|
+
assert_equal('2.6', f.to_s(2.6))
|
107
|
+
assert_equal('2.0', f.to_s(2))
|
108
|
+
|
109
|
+
assert_raises(ArgumentError) do
|
110
|
+
f.to_type('')
|
111
|
+
end
|
112
|
+
assert_raises(ArgumentError) do
|
113
|
+
f.to_type('asas')
|
114
|
+
end
|
115
|
+
assert_nil(f.to_type(nil))
|
116
|
+
assert_equal(2.0, f.to_type('2'))
|
117
|
+
assert_equal(2.6, f.to_type('2.6'))
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_ints_formatter
|
121
|
+
f = Formatters::IntsFormatter.new
|
122
|
+
assert_equal(nil, f.to_s(nil))
|
123
|
+
assert_equal('1', f.to_s(1))
|
124
|
+
assert_equal('1', f.to_s([1]))
|
125
|
+
assert_equal('1,2', f.to_s([1,2]))
|
126
|
+
|
127
|
+
assert_equal(nil, f.to_type(nil))
|
128
|
+
assert_equal([], f.to_type(''))
|
129
|
+
assert_equal([1], f.to_type('1'))
|
130
|
+
assert_equal([1,2], f.to_type('1,2'))
|
131
|
+
end
|
132
|
+
def test_floats_formatter
|
133
|
+
f = Formatters::FloatsFormatter.new
|
134
|
+
assert_equal(nil, f.to_s(nil))
|
135
|
+
assert_equal('1.2', f.to_s(1.2))
|
136
|
+
assert_equal('1.2', f.to_s([1.2]))
|
137
|
+
assert_equal('1.2,1.3', f.to_s([1.2,1.3]))
|
138
|
+
|
139
|
+
assert_equal(nil, f.to_type(nil))
|
140
|
+
assert_equal([], f.to_type(''))
|
141
|
+
assert_equal([1.2], f.to_type('1.2'))
|
142
|
+
assert_equal([1.2,1.3], f.to_type('1.2,1.3'))
|
143
|
+
assert_equal([1.2,1.3], f.to_type('1.2, 1.3'))
|
144
|
+
end
|
145
|
+
def test_booleans_formatter
|
146
|
+
f = Formatters::BooleansFormatter.new
|
147
|
+
assert_equal(nil, f.to_s(nil))
|
148
|
+
assert_equal('1', f.to_s(true))
|
149
|
+
assert_equal('1', f.to_s([true]))
|
150
|
+
assert_equal('1,0', f.to_s([true,false]))
|
151
|
+
assert_equal('0,0,0,0,', f.to_s(['', 0, false, '0', nil]))
|
152
|
+
|
153
|
+
assert_equal(nil, f.to_type(nil))
|
154
|
+
assert_equal([], f.to_type(''))
|
155
|
+
assert_equal([true], f.to_type('1'))
|
156
|
+
assert_equal([true, false], f.to_type('1,0'))
|
157
|
+
|
158
|
+
|
159
|
+
# test boolean with values != true|false
|
160
|
+
assert_equal('1', f.to_s('true'))
|
161
|
+
assert_equal('1', f.to_s(555))
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def test_strict_booleans_formatter
|
166
|
+
f = Formatters::StrictBooleansFormatter.new
|
167
|
+
assert_equal(nil, f.to_s(nil))
|
168
|
+
assert_equal('1', f.to_s(true))
|
169
|
+
assert_equal('1', f.to_s([true]))
|
170
|
+
assert_equal('1,0', f.to_s([true,false]))
|
171
|
+
assert_equal('1,1', f.to_s([1,0]))
|
172
|
+
|
173
|
+
assert_equal(nil, f.to_type(nil))
|
174
|
+
assert_equal([], f.to_type(''))
|
175
|
+
assert_equal([true], f.to_type('1'))
|
176
|
+
assert_equal([true, false], f.to_type('1,0'))
|
177
|
+
|
178
|
+
|
179
|
+
# test boolean with values != true|false
|
180
|
+
assert_equal('1', f.to_s('true'))
|
181
|
+
assert_equal('1', f.to_s(555))
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class HasSettingTest < Test::Unit::TestCase
|
4
|
+
def setup()
|
5
|
+
@foo = Foo.create!
|
6
|
+
@bar = Bar.create!
|
7
|
+
@baz = Baz.create!
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def test_setting_has_accessors
|
12
|
+
assert @foo.respond_to?(:setting_1)
|
13
|
+
assert @foo.respond_to?(:setting_1=)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def test_has_many
|
18
|
+
assert @foo.respond_to?(:settings)
|
19
|
+
assert @foo.settings.is_a?(Array)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_settings_are_destroyed
|
23
|
+
count_before = HasSetting::Setting.count
|
24
|
+
@foo.setting_1 = 10
|
25
|
+
@foo.save!
|
26
|
+
assert_equal(count_before + 1, HasSetting::Setting.count)
|
27
|
+
@foo.destroy
|
28
|
+
assert_equal(count_before, HasSetting::Setting.count)
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_write_setting
|
33
|
+
count_before = HasSetting::Setting.count
|
34
|
+
@foo.write_setting('name', 'value1')
|
35
|
+
@foo.save!
|
36
|
+
assert_equal(count_before + 1, HasSetting::Setting.count)
|
37
|
+
setting = @foo.read_setting('name')
|
38
|
+
assert setting
|
39
|
+
assert_equal('value1', setting.value)
|
40
|
+
|
41
|
+
@foo.write_setting('name', 'value2')
|
42
|
+
assert_equal(count_before + 1, HasSetting::Setting.count)
|
43
|
+
setting = @foo.read_setting('name')
|
44
|
+
assert setting
|
45
|
+
assert_equal('value2', setting.value)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_setting_accessors
|
49
|
+
count_before = HasSetting::Setting.count
|
50
|
+
assert(!@foo.setting_1)
|
51
|
+
@foo.setting_1 = 'bli'
|
52
|
+
@foo.save!
|
53
|
+
assert_equal(count_before + 1, HasSetting::Setting.count)
|
54
|
+
assert_equal('bli', @foo.setting_1)
|
55
|
+
@foo.setting_1 = 'bla'
|
56
|
+
assert_equal('bla', @foo.setting_1)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_different_classes_do_not_share_setting
|
60
|
+
count_before = HasSetting::Setting.count
|
61
|
+
@foo.setting_1 = 'foo'
|
62
|
+
@foo.save!
|
63
|
+
@bar.setting_1 = 'bar'
|
64
|
+
@bar.save!
|
65
|
+
assert_equal(count_before + 2, HasSetting::Setting.count)
|
66
|
+
|
67
|
+
assert_equal('foo', @foo.setting_1)
|
68
|
+
assert_equal('bar', @bar.setting_1)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def test_has_nil_setting
|
73
|
+
@foo.setting_1 = nil
|
74
|
+
assert(@foo.read_setting('setting_1'))
|
75
|
+
assert(!@foo.setting_1)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_options_on_getter
|
79
|
+
@foo.setting_1 = '12.3'
|
80
|
+
assert_equal('12.3', @foo.setting_1)
|
81
|
+
assert_equal(12, @foo.setting_1(:type => :int))
|
82
|
+
assert_equal(12.3, @foo.setting_1(:type => :float))
|
83
|
+
|
84
|
+
# Foo.setting_2 is a float setting. Override and get as string
|
85
|
+
@foo.setting_2 = 12.3
|
86
|
+
assert_equal('12.3', @foo.setting_2(:type => :string))
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_different_classes_do_not_share_options()
|
90
|
+
@foo.setting_2 = 12.3
|
91
|
+
assert_equal(12.3, @foo.setting_2)
|
92
|
+
@bar.setting_2 = 12.3
|
93
|
+
assert_equal(12, @bar.setting_2)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_default_values()
|
97
|
+
assert_equal('def', @foo.with_default)
|
98
|
+
assert_equal('override def', @foo.with_default(:default => 'override def'))
|
99
|
+
@foo.with_default = 'not def'
|
100
|
+
assert_equal('not def', @foo.with_default)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_write_settings_without_saved_parent
|
104
|
+
my_foo = Foo.new
|
105
|
+
count_before = HasSetting::Setting.count
|
106
|
+
my_foo.with_default = 'radabumm'
|
107
|
+
assert_equal(count_before, HasSetting::Setting.count)
|
108
|
+
assert_equal('radabumm', my_foo.with_default)
|
109
|
+
my_foo.save!
|
110
|
+
assert_equal(count_before + 1, HasSetting::Setting.count)
|
111
|
+
assert_equal('radabumm', my_foo.with_default)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_not_everyone_has_settings_association
|
115
|
+
assert_equal(true, @foo.respond_to?(:settings))
|
116
|
+
assert_equal(true, @bar.respond_to?(:settings))
|
117
|
+
assert_equal(false, @baz.respond_to?(:settings))
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def test_boolean_setting_without_default
|
122
|
+
assert_equal nil, @foo.setting_3
|
123
|
+
@foo.setting_3 = true
|
124
|
+
@foo.save!
|
125
|
+
|
126
|
+
@foo = Foo.find @foo.id
|
127
|
+
assert_equal true, @foo.setting_3
|
128
|
+
end
|
129
|
+
def test_boolean_setting_with_default
|
130
|
+
assert_equal false, @foo.setting_4
|
131
|
+
@foo.setting_4 = true
|
132
|
+
@foo.save!
|
133
|
+
@foo = Foo.find @foo.id
|
134
|
+
assert_equal true, @foo.setting_4
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_boolean_setting_with_default_and_no_saving
|
138
|
+
assert_equal false, @foo.setting_4
|
139
|
+
@foo.setting_4 = true
|
140
|
+
assert_equal true, @foo.setting_4
|
141
|
+
@foo.setting_4 = nil
|
142
|
+
assert_equal nil, @foo.setting_4
|
143
|
+
@foo.setting_4 = false
|
144
|
+
assert_equal false, @foo.setting_4
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_setting
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.6
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Simplificator GmbH
|
9
|
+
- Nico Ritsche
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-04-10 00:00:00.000000000 Z
|
14
|
+
dependencies: []
|
15
|
+
description: Stores settings as key/value pairs in a settings table and provides accessors
|
16
|
+
for them on the owning object
|
17
|
+
email:
|
18
|
+
- info@simplificator.com
|
19
|
+
- ncrdevmail@gmail.com
|
20
|
+
executables: []
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- .gitignore
|
25
|
+
- README
|
26
|
+
- Rakefile
|
27
|
+
- VERSION.yml
|
28
|
+
- has_setting.gemspec
|
29
|
+
- help/001_create_settings.rb
|
30
|
+
- lib/has_setting.rb
|
31
|
+
- lib/has_setting/ar_extensions.rb
|
32
|
+
- lib/has_setting/formatters.rb
|
33
|
+
- lib/has_setting/setting.rb
|
34
|
+
- lib/has_setting/version.rb
|
35
|
+
- test/bar.rb
|
36
|
+
- test/baz.rb
|
37
|
+
- test/foo.rb
|
38
|
+
- test/test_helper.rb
|
39
|
+
- test/unit/formatters_test.rb
|
40
|
+
- test/unit/has_setting_test.rb
|
41
|
+
homepage: http://github.com/ncri/has_setting
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.24
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Simple setting extension to AR
|
66
|
+
test_files:
|
67
|
+
- test/bar.rb
|
68
|
+
- test/baz.rb
|
69
|
+
- test/foo.rb
|
70
|
+
- test/test_helper.rb
|
71
|
+
- test/unit/formatters_test.rb
|
72
|
+
- test/unit/has_setting_test.rb
|
73
|
+
has_rdoc: true
|