arsettings 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,91 @@
1
+ task :default => :test
2
+
3
+ desc 'run the unit tests'
4
+ task :test do
5
+ query = File.dirname(__FILE__) << '/test/*_test.rb'
6
+ Dir[query].each { |filename| require filename }
7
+ end
8
+
9
+ desc 'irb session with env loaded'
10
+ task :console do
11
+ dir = File.dirname(__FILE__)
12
+ requirements = String.new
13
+ requirements << "-r #{dir}/test/_helper.rb"
14
+ system "irb -f #{requirements}"
15
+ end
16
+
17
+ require "rubygems"
18
+ require "rake/gempackagetask"
19
+ require "rake/rdoctask"
20
+
21
+ require "rake/testtask"
22
+ Rake::TestTask.new do |t|
23
+ t.libs << "test"
24
+ t.test_files = FileList["test/**/*_test.rb"]
25
+ t.verbose = true
26
+ end
27
+
28
+
29
+
30
+ # This builds the actual gem. For details of what all these options
31
+ # mean, and other ones you can add, check the documentation here:
32
+ #
33
+ # http://rubygems.org/read/chapter/20
34
+ #
35
+ spec = Gem::Specification.new do |s|
36
+
37
+ # Change these as appropriate
38
+ s.name = "arsettings"
39
+ s.version = "1.0.0"
40
+ s.author = "Joshua Cheek"
41
+ s.email = "josh.cheek@gmail.com"
42
+ s.homepage = "https://github.com/JoshCheek/ARSettings"
43
+ s.summary = "Settings for ActiveRecord projects (ie Rails)"
44
+ s.description = "ActiveRecord has a lot of support for tables of similar values. But what about those one time only values, like site settings? This is what ARSettings is intended for. One line to add settings to your ActiveRecord classes. Two to non-ActiveRecord classes. And you can have settings that are not defined on any class as well."
45
+
46
+ s.has_rdoc = true
47
+ s.extra_rdoc_files = %w(Readme.mdown)
48
+ s.rdoc_options = %w(--main Readme.mdown)
49
+
50
+ # Add any extra files to include in the gem
51
+ s.files = %w(Rakefile Readme.mdown) + Dir.glob("{test,lib/**/*}")
52
+ s.require_paths = ["lib"]
53
+
54
+ # If you want to depend on other gems, add them here, along with any
55
+ # relevant versions
56
+ s.add_dependency("activerecord", ">= 2.3.8")
57
+
58
+ # If your tests use any gems, include them here
59
+ s.add_development_dependency("sqlite-ruby", ">= 1.3.1")
60
+ end
61
+
62
+ # This task actually builds the gem. We also regenerate a static
63
+ # .gemspec file, which is useful if something (i.e. GitHub) will
64
+ # be automatically building a gem for this project. If you're not
65
+ # using GitHub, edit as appropriate.
66
+ #
67
+ # To publish your gem online, install the 'gemcutter' gem; Read more
68
+ # about that here: http://gemcutter.org/pages/gem_docs
69
+ Rake::GemPackageTask.new(spec) do |pkg|
70
+ pkg.gem_spec = spec
71
+ end
72
+
73
+ desc "Build the gemspec file #{spec.name}.gemspec"
74
+ task :gemspec do
75
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
76
+ File.open(file, "w") {|f| f << spec.to_ruby }
77
+ end
78
+
79
+ task :package => :gemspec
80
+
81
+ # Generate documentation
82
+ Rake::RDocTask.new do |rd|
83
+ rd.main = "Readme.mdown"
84
+ rd.rdoc_files.include("Readme.mdown", "lib/**/*.rb")
85
+ rd.rdoc_dir = "rdoc"
86
+ end
87
+
88
+ desc 'Clear out RDoc and generated packages'
89
+ task :clean => [:clobber_rdoc, :clobber_package] do
90
+ rm "#{spec.name}.gemspec"
91
+ end
data/Readme.mdown ADDED
@@ -0,0 +1,54 @@
1
+ Description
2
+ ===========
3
+
4
+ Usage
5
+ =====
6
+
7
+
8
+ Test
9
+ ====
10
+
11
+ $ rake
12
+
13
+
14
+ Dependencies
15
+ ============
16
+
17
+ * [ActiveRecord](http://rubygems.org/gems/activerecord)
18
+ * [sqlite-ruby 1.3.1](http://rubygems.org/gems/sqlite-ruby)
19
+
20
+
21
+ TODO
22
+ ====
23
+
24
+ * Write examples
25
+ * Write documentation
26
+ * Turn into gem
27
+
28
+ ---------------------------------------
29
+
30
+ **This code is unmaintained.**
31
+
32
+ _If you do something interesting with it, let me know so I can be happy._
33
+
34
+ ---------------------------------------
35
+
36
+ Copyright (c) 2010 Joshua Cheek
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining a copy
39
+ of this software and associated documentation files (the "Software"), to deal
40
+ in the Software without restriction, including without limitation the rights
41
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42
+ copies of the Software, and to permit persons to whom the Software is
43
+ furnished to do so, subject to the following conditions:
44
+
45
+ The above copyright notice and this permission notice shall be included in
46
+ all copies or substantial portions of the Software.
47
+
48
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54
+ THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ class ActiveRecord::Base
2
+
3
+ def self.has_setting( name , options=Hash.new , &block)
4
+ raise NoDefaultPackageError.new("No default settings class is set (make sure you have already invoked create_settings_class)") unless ARSettings.default_class
5
+ ARSettings.validate_options options , :default , :volatile , :instance
6
+ package = ARSettings.default_class.package(self)
7
+ getter = name
8
+ setter = "#{name}="
9
+ boolean_getter = "#{name}?"
10
+ (class << self ; self ; end).instance_eval do
11
+ define_method getter do package.send getter end
12
+ define_method boolean_getter do package.send boolean_getter end
13
+ define_method setter do |arg| package.send setter , arg end
14
+ end
15
+ if options.delete :instance
16
+ define_method getter do package.send getter end
17
+ define_method boolean_getter do package.send boolean_getter end
18
+ define_method setter do |arg| package.send setter , arg end
19
+ end
20
+ package.add name , options , &block
21
+ end
22
+
23
+ end
@@ -0,0 +1,74 @@
1
+ module ARSettings
2
+
3
+ AlreadyDefinedError = Class.new(Exception)
4
+ NoSuchSettingError = Class.new(Exception)
5
+ InvalidNameError = Class.new(Exception)
6
+ InvalidPackageError = Class.new(Exception)
7
+ InvalidOptionError = Class.new(Exception)
8
+ NoDefaultPackageError = Class.new(Exception)
9
+ UninitializedSettingError = Class.new(Exception)
10
+
11
+ # create the settings class
12
+ def self.create_settings_class( classname , options=Hash.new )
13
+ raise AlreadyDefinedError.new("you are trying to define the settings class #{classname}, but it already exists") if Object.constants.map { |c| c.to_s }.include?(classname.to_s)
14
+ validate_options options , :volatile , :max_chars
15
+ Object.const_set classname , Class.new(ActiveRecord::Base)
16
+ klass = Object.const_get(classname).class_eval do
17
+ extend SettingsClass_ClassMethods
18
+ include SettingsClass_InstanceMethods
19
+ const_set :MAX_CHARS , options.fetch( :max_chars , 30 )
20
+ const_set :VOLATILIE_DEFAULT , options.fetch( :volatile , false )
21
+ send :load_from_db
22
+ self
23
+ end
24
+ @default_class ||= klass
25
+ klass
26
+ end
27
+
28
+ class << self
29
+ attr_accessor :default_class
30
+ end
31
+
32
+ # can be used to put settings on any object
33
+ def self.on( object , options = Hash.new )
34
+ settings_class = options.fetch :settings_class , default_class
35
+ raise NoDefaultPackageError.new("You did not specify a settings class, and no default is set (make sure you have already invoked create_settings_class)") unless settings_class
36
+ validate_options options , :settings_class
37
+ (class << object ; self ; end).send :define_method , :has_setting do |name,inner_options={},&block|
38
+ instance = inner_options.delete :instance
39
+ package = settings_class.package(object)
40
+ package.add name , inner_options , &block
41
+ getter = name
42
+ setter = "#{name}="
43
+ boolean_getter = "#{name}?"
44
+ (class << self ; self ; end).instance_eval do
45
+ define_method getter do package.send getter end
46
+ define_method setter do |arg| package.send setter , arg end
47
+ define_method boolean_getter do package.send boolean_getter end
48
+ end
49
+ if instance
50
+ define_method getter do package.send getter end
51
+ define_method boolean_getter do package.send boolean_getter end
52
+ define_method setter do |arg| package.send setter , arg end
53
+ end
54
+ end
55
+ end
56
+
57
+ def self.serialize(data)
58
+ YAML::dump(data)
59
+ end
60
+
61
+ def self.deserialize(data)
62
+ YAML::load(data)
63
+ end
64
+
65
+ def self.validate_options(options,*valid_options)
66
+ options.each do |option,value|
67
+ unless valid_options.include? option
68
+ raise ARSettings::InvalidOptionError.new "#{option.inspect} is not a valid option, because it is not in #{valid_options.inspect}"
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+
@@ -0,0 +1,165 @@
1
+ module ARSettings
2
+ class Packaged
3
+
4
+ InvalidSettingsClassError = Class.new Exception
5
+ PASSTHROUGH = lambda { |val| val }
6
+
7
+ attr_reader :package , :settings_class
8
+
9
+ private_class_method :new
10
+
11
+ def self.instance(settings_class,package)
12
+ validate(settings_class,package)
13
+ package = normalize package
14
+ @instances ||= Hash.new
15
+ @instances[settings_class] ||= Hash.new
16
+ @instances[settings_class][package] ||= new(settings_class,package)
17
+ end
18
+
19
+ def self.has_instance?(settings_class,package)
20
+ @instances && @instances[settings_class] && @instances[settings_class][normalize package]
21
+ end
22
+
23
+ def self.validate(settings_class,package)
24
+ validate_package(package)
25
+ validate_settings_class(settings_class)
26
+ end
27
+
28
+ def self.validate_package(package)
29
+ raise ARSettings::InvalidPackageError.new("#{package.inspect} should be a String/Symbol/Class") unless
30
+ String === package || Symbol === package || Class === package
31
+ end
32
+
33
+ def self.validate_settings_class(settings_class)
34
+ raise InvalidSettingsClassError.new("#{settings_class.inspect} should be a class created by the create_settings_class method") unless
35
+ Class === settings_class && settings_class.superclass == ActiveRecord::Base && SettingsClass_ClassMethods === settings_class
36
+ end
37
+
38
+ def self.instances(settings_class)
39
+ @instances ||= Hash.new
40
+ @instances[settings_class] || Hash.new
41
+ end
42
+
43
+ def self.normalize(package)
44
+ return package if Symbol === package
45
+ package.to_s.to_sym
46
+ end
47
+
48
+
49
+ # instance methods
50
+
51
+ def initialize(settings_class,package)
52
+ @package , @settings_class , @settings = package.to_s.to_sym , settings_class , Hash.new
53
+ end
54
+
55
+ def reset
56
+ (@settings||{}).each do |name,instance|
57
+ remove_setter(name)
58
+ remove_getter(name)
59
+ remove_boolean_getter(name)
60
+ instance.destroy
61
+ end
62
+ @settings = Hash.new
63
+ end
64
+
65
+ def validate_name(name)
66
+ raise ARSettings::InvalidNameError.new("#{name} is #{name.to_s.size}, but MAX_CHARS is set to #{settings_class::MAX_CHARS}") if name.to_s.length > settings_class::MAX_CHARS
67
+ regex = /\A[a-z_][a-zA-Z_]*\Z/m
68
+ raise ARSettings::InvalidNameError.new("#{name.inspect} is not a valid settings name, because it is not a valid method name since it does not match #{regex.inspect}") if name.to_s !~ regex
69
+ end
70
+
71
+ def add( name , options={} , &proc )
72
+ return(add_from_instance name[:record]) if name.is_a? Hash # internal use only
73
+ ARSettings.validate_options options , :volatile , :default
74
+ name = name.to_sym
75
+ validate_name(name)
76
+ if setting? name
77
+ @settings[name].volatile = options[:volatile] if options.has_key? :volatile
78
+ @settings[name].postprocessing = proc if proc
79
+ else
80
+ add_setter(name)
81
+ add_getter(name)
82
+ add_boolean_getter(name)
83
+ @settings[name] = settings_class.new :name => name.to_s , :postprocessing => proc || PASSTHROUGH , :volatile => !!options.fetch(:volatile,settings_class::VOLATILIE_DEFAULT) , :package => package
84
+ send "#{name}=" , options[:default] if options.has_key?(:default)
85
+ end
86
+ self
87
+ end
88
+
89
+ def setting?(name)
90
+ @settings.has_key? name
91
+ end
92
+
93
+ def metaclass
94
+ class << self
95
+ self
96
+ end
97
+ end
98
+
99
+ def define_method( name , &body )
100
+ metaclass.send :define_method , name , &body
101
+ end
102
+
103
+ def remove_method(name)
104
+ metaclass.send :remove_method , "#{setting}="
105
+ end
106
+
107
+ def add_setter(name)
108
+ define_method "#{name}=" do |value|
109
+ @settings[name].value = @settings[name].postprocessing.call(value)
110
+ end
111
+ end
112
+
113
+ def add_getter(name)
114
+ define_method(name) { @settings[name].value }
115
+ end
116
+
117
+ def add_boolean_getter(name)
118
+ define_method("#{name}?") { !!@settings[name].value }
119
+ end
120
+
121
+ def remove_setter(name)
122
+ metaclass.send :remove_method , "#{name}="
123
+ end
124
+
125
+ def remove_getter(name)
126
+ metaclass.send :remove_method , name
127
+ end
128
+
129
+ def remove_boolean_getter(name)
130
+ metaclass.send :remove_method , "#{name}?"
131
+ end
132
+
133
+ def method_missing(name,*args)
134
+ if name.to_s =~ /\A[A-Z]/
135
+ settings_class.const_get name , *args
136
+ elsif name.to_s !~ /=$/ || ( name.to_s =~ /=$/ && args.size == 1 )
137
+ raise ARSettings::NoSuchSettingError.new("There is no setting named #{name.to_s.chomp '='}")
138
+ else
139
+ super
140
+ end
141
+ end
142
+
143
+ def settings
144
+ @settings.keys
145
+ end
146
+
147
+ def settings_with_values
148
+ @settings.map { |name,instance| [name,instance.value] }
149
+ end
150
+
151
+ private
152
+
153
+ def add_from_instance(record)
154
+ record.postprocessing = PASSTHROUGH
155
+ name = record.name.to_sym
156
+ validate_name(name)
157
+ add_setter(name)
158
+ add_getter(name)
159
+ add_boolean_getter(name)
160
+ @settings[name] = record
161
+ end
162
+
163
+ end
164
+ end
165
+
@@ -0,0 +1,39 @@
1
+ module ARSettings
2
+ module SettingsClass_ClassMethods
3
+
4
+ def reset_all
5
+ Packaged.instances(self).each { |name,package| package.reset }
6
+ end
7
+
8
+ def package(package)
9
+ Packaged.instance self , package
10
+ end
11
+
12
+ def add( name , options={} , &proc )
13
+ options = name if name.is_a? Hash
14
+ if options[:package]
15
+ package options.delete(:package)
16
+ else
17
+ package self
18
+ end.add( name , options , &proc )
19
+ end
20
+
21
+ def method_missing(name,*args)
22
+ if name =~ /\A[A-Z]/
23
+ const_get name , *args
24
+ elsif name.to_s !~ /=$/ || ( name.to_s =~ /=$/ && args.size == 1 )
25
+ package(self).send name , *args
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def load_from_db
34
+ reset_all
35
+ all.each { |instance| add :record => instance , :package => instance.package }
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ module ARSettings
2
+
3
+ module SettingsClass_InstanceMethods
4
+
5
+ # unfortunately, can't serialize a proc. I tried both yaml and marshal
6
+ # so will have to keep it in memory and just be sure to set it each time app loads
7
+ attr_accessor :postprocessing
8
+
9
+
10
+ def value=(new_value)
11
+ @deserialized_value = new_value
12
+ @value_is_deserialized = true
13
+ super ARSettings.serialize(new_value)
14
+ save
15
+ end
16
+
17
+ def value
18
+ if @value_is_deserialized && !volatile?
19
+ @deserialized_value
20
+ else
21
+ raise UninitializedSettingError.new("#{package}##{name} has not been initialized.") unless super
22
+ reload
23
+ @value_is_deserialized = true
24
+ @deserialized_value = ARSettings.deserialize(super)
25
+ @deserialized_value
26
+ end
27
+ end
28
+
29
+ def volatile=(value)
30
+ super
31
+ save
32
+ end
33
+
34
+ def package
35
+ @package ||= super.to_sym
36
+ end
37
+
38
+ def package=(pkg)
39
+ super pkg.to_s
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
data/lib/arsettings.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+
3
+ manifest = %w(
4
+ arsettings
5
+ settings_class_methods
6
+ settings_instance_methods
7
+ packaged
8
+ activerecord
9
+ )
10
+
11
+ manifest.each do |filename|
12
+ require File.dirname(__FILE__) << "/arsettings/#{filename}"
13
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arsettings
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Joshua Cheek
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-07 01:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 19
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 8
34
+ version: 2.3.8
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: sqlite-ruby
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 25
46
+ segments:
47
+ - 1
48
+ - 3
49
+ - 1
50
+ version: 1.3.1
51
+ type: :development
52
+ version_requirements: *id002
53
+ description: ActiveRecord has a lot of support for tables of similar values. But what about those one time only values, like site settings? This is what ARSettings is intended for. One line to add settings to your ActiveRecord classes. Two to non-ActiveRecord classes. And you can have settings that are not defined on any class as well.
54
+ email: josh.cheek@gmail.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - Readme.mdown
61
+ files:
62
+ - Rakefile
63
+ - Readme.mdown
64
+ - lib/arsettings/activerecord.rb
65
+ - lib/arsettings/arsettings.rb
66
+ - lib/arsettings/packaged.rb
67
+ - lib/arsettings/settings_class_methods.rb
68
+ - lib/arsettings/settings_instance_methods.rb
69
+ - lib/arsettings.rb
70
+ has_rdoc: true
71
+ homepage: https://github.com/JoshCheek/ARSettings
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --main
77
+ - Readme.mdown
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.7
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Settings for ActiveRecord projects (ie Rails)
105
+ test_files: []
106
+