has_setting 0.4.6
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/.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
|