property_sets 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.1"
12
+ gem "rcov", ">= 0"
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Morten Primdahl
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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Morten Primdahl
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.
@@ -0,0 +1,101 @@
1
+ = Property sets
2
+
3
+ The property_set gem is an evolution of the has_settings gem which was an evolution of the features gem. This is getting old.
4
+
5
+ This gem is a way for you to use a basic "key/value" store for storing attributes for a given model in a relational fashion where there's a row per attribute. Alternatively you'd need to add a new column per attribute to your main table, or serialize the attributes and their values.
6
+
7
+ == Description
8
+
9
+ You configure the allowed stored properties by specifying these in an initializer:
10
+
11
+ class Account < ActiveRecord::Base
12
+ property_set :settings do
13
+ property :version, :default => "v1.0"
14
+ property :product
15
+ end
16
+
17
+ property_set :texts do
18
+ property :epilogue
19
+ end
20
+ end
21
+
22
+ The declared properties can then be accessed runtime via the defined association:
23
+
24
+ account.settings.ssl # returns the value of the setting
25
+ account.settings.ssl=(value) # immediately changes the value of the setting
26
+ account.settings.ssl? # coerces the setting to boolean AR style
27
+
28
+ If the value has never been set, a nil (or default) is returned. And that's pretty much it.
29
+
30
+ Stored properties can also be updated with the update_attributes and update_attributes! methods.
31
+
32
+ # Assuming params include { :account => { :settings => { :version => "v2.0", :forums => '0' }}}
33
+ # the below would set the respective values on the account
34
+ account.update_attributes(params[:account])
35
+
36
+ We also created a view helper to help you generate the UI for enabling and disabling properties:
37
+
38
+ <% form_for(:account, :html => { :method => :put }) do |f| %>
39
+ <h3>
40
+ <%= f.property_set_check_box(:settings) :forums %> Enable forums on your account
41
+ </h3>
42
+ <% end %>
43
+
44
+ == Security
45
+
46
+ We support protection from mass updates, the syntax is as follows:
47
+
48
+ class Account < ActiveRecord::Base
49
+ property_set :settings do
50
+ property :product, :protected => true
51
+ end
52
+ end
53
+
54
+ == Installation
55
+
56
+ Install the gem in your rails project by putting it in your Gemfile:
57
+
58
+ gem 'property_set'
59
+
60
+ Also remember to create the storage table, if for example you are going to be using this with an accounts model and have, you can should the table like:
61
+
62
+ create_table :account_settings do |t|
63
+ t.integer :account_id, :null => false
64
+ t.string :name, :null => false
65
+ t.string :value
66
+ t.timestamps
67
+ end
68
+
69
+ add_index :account_settings, [ :account_id, :name ], :unique => true
70
+
71
+ == Requirements
72
+
73
+ * ActiveRecord
74
+ * ActionPack
75
+
76
+ == LICENSE:
77
+
78
+ (The MIT License)
79
+
80
+ Copyright (c) 2010 Zendesk
81
+
82
+ Permission is hereby granted, free of charge, to any person
83
+ obtaining a copy of this software and associated documentation
84
+ files (the "Software"), to deal in the Software without
85
+ restriction, including without limitation the rights to use,
86
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
87
+ copies of the Software, and to permit persons to whom the
88
+ Software is furnished to do so, subject to the following
89
+ conditions:
90
+
91
+ The above copyright notice and this permission notice shall be
92
+ included in all copies or substantial portions of the Software.
93
+
94
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
95
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
96
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
97
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
98
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
99
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
100
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
101
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "property_sets"
8
+ gem.summary = %Q{Property sets for ActiveRecord}
9
+ gem.description = %Q{This gem is an ActiveRecord extension which provides a convenient interface for managing per row properties}
10
+ gem.email = "morten@zendesk.com"
11
+ gem.homepage = "http://github.com/morten/property_sets"
12
+ gem.authors = ["Morten Primdahl"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "property_sets #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.6
@@ -0,0 +1,20 @@
1
+ require 'property_sets/property_set_model'
2
+ require 'property_sets/active_record_extension'
3
+ require 'property_sets/property_set_helper'
4
+
5
+ module PropertySets
6
+ def self.ensure_property_set_class(association, owner_class)
7
+ const_name = "#{owner_class.name}#{association.to_s.singularize.capitalize}".to_sym
8
+ unless Object.const_defined?(const_name)
9
+ property_class = Object.const_set(const_name, Class.new(ActiveRecord::Base))
10
+ property_class.class_eval do
11
+ include PropertySets::PropertySetModel::InstanceMethods
12
+ extend PropertySets::PropertySetModel::ClassMethods
13
+ end
14
+
15
+ property_class.owner_class = owner_class
16
+ property_class.owner_assoc = association
17
+ end
18
+ Object.const_get(const_name)
19
+ end
20
+ end
@@ -0,0 +1,58 @@
1
+ module PropertySets
2
+ module ActiveRecordExtension
3
+ module ClassMethods
4
+ def property_set(association, &block)
5
+ raise "Invalid association name, letters only" unless association.to_s =~ /[a-z]+/
6
+ property_class = PropertySets.ensure_property_set_class(association, self)
7
+ property_class.instance_eval(&block)
8
+
9
+ has_many association.to_s.pluralize.to_sym, :class_name => property_class.name, :dependent => :destroy do
10
+
11
+ # Accepts a name value pair hash { :name => 'value', :pairs => true } and builds a property for each key
12
+ def build(property_pairs)
13
+ property_pairs.keys.each do |name|
14
+ value = property_pairs[name]
15
+ self << proxy_reflection.klass.new(:name => name.to_s, :value => value)
16
+ end
17
+ end
18
+
19
+ # Define the settings query methods, e.g. +account.settings.wiffle?+
20
+ property_class.keys.each do |key|
21
+ raise "Invalid key #{key}" if self.respond_to?(key)
22
+
23
+ # Reports the coerced truth valye of the property
24
+ define_method "#{key}?" do
25
+ lookup(key).true?
26
+ end
27
+
28
+ # Assigns a new value to the property
29
+ define_method "#{key}=" do |value|
30
+ instance = lookup(key)
31
+ instance.value = value
32
+ instance.save
33
+ end
34
+
35
+ # Returns the value of the property
36
+ define_method "#{key}" do
37
+ lookup(key)
38
+ end
39
+
40
+ # The finder method which returns the property if present, otherwise a new instance with defaults
41
+ define_method "lookup" do |key|
42
+ instance = detect { |property| property.name.to_sym == key }
43
+ instance ||= property_class.new(@owner.class.name.underscore.to_sym => @owner, :name => key.to_s, :value => property_class.default(key))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def self.included(receiver)
51
+ receiver.extend(ClassMethods)
52
+ end
53
+ end
54
+ end
55
+
56
+ ActiveRecord::Base.class_eval do
57
+ include PropertySets::ActiveRecordExtension
58
+ end
@@ -0,0 +1,19 @@
1
+ module ActionView
2
+ module Helpers
3
+ def setting_check_box(model_name, method, options = {}, checked_value = "1", unchecked_value = "0")
4
+ the_model = @template.instance_variable_get("@#{model_name}")
5
+ throw "No @#{model_name} in scope" if the_model.nil?
6
+ throw "The setting_check_box only works on models with settings" unless the_model.respond_to?(:settings)
7
+ options[:checked] = the_model.settings.send("#{method}?")
8
+ options[:id] ||= "#{model_name}_settings_#{method}"
9
+ options[:name] = "#{model_name}[settings][#{method}]"
10
+ @template.check_box(model_name, "settings_#{method}", options, checked_value, unchecked_value)
11
+ end
12
+
13
+ class FormBuilder
14
+ def setting_check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
15
+ @template.setting_check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,97 @@
1
+ module PropertySets
2
+ module PropertySetModel
3
+ module InstanceMethods
4
+ def protected?
5
+ self.class.protected?(self.name.to_sym)
6
+ end
7
+
8
+ def false?
9
+ [ "false", "0", "", "off", "n" ].member?(value.to_s.downcase)
10
+ end
11
+
12
+ def true?
13
+ !false?
14
+ end
15
+
16
+ def to_s
17
+ value.to_s
18
+ end
19
+
20
+ private
21
+
22
+ def validate_format_of_name
23
+ if name.blank?
24
+ errors.add(:name, :blank)
25
+ elsif !name.is_a?(String) || name !~ /^([a-z0-9]+_?)+$/
26
+ errors.add(:name, :invalid)
27
+ end
28
+ end
29
+
30
+ def owner_class_instance
31
+ send(self.class.owner_class_sym)
32
+ end
33
+
34
+ def update_owner_timestamp
35
+ owner_class_instance.update_attribute(:updated_at, Time.now) if owner_class_instance && !owner_class_instance.new_record?
36
+ end
37
+
38
+ def reset_owner_association
39
+ owner_class_instance.send(self.class.owner_assoc).reload
40
+ end
41
+ end
42
+
43
+ module ClassMethods
44
+ def self.extended(base)
45
+ base.after_create :reset_owner_association
46
+ base.after_destroy :reset_owner_association
47
+ base.validate :validate_format_of_name
48
+ end
49
+
50
+ def property(key, options = nil)
51
+ @properties ||= {}
52
+ @properties[key] = options
53
+ end
54
+
55
+ def keys
56
+ @properties.keys
57
+ end
58
+
59
+ def protected?(key)
60
+ !!@properties[key][:protected]
61
+ end
62
+
63
+ def default(key)
64
+ @properties[key] && @properties[key].key?(:default) ? @properties[key][:default] : nil
65
+ end
66
+
67
+ def owner_class=(owner_class)
68
+ @owner_class_sym = owner_class.name.underscore.to_sym
69
+ belongs_to owner_class_sym
70
+ validates_presence_of owner_class_sym
71
+ validates_uniqueness_of :name, :scope => owner_class_key_sym
72
+
73
+ if owner_class.table_exists? && owner_class.column_names.include?("updated_at")
74
+ before_create :update_owner_timestamp
75
+ before_destroy :update_owner_timestamp
76
+ end
77
+ end
78
+
79
+ def owner_assoc=(association)
80
+ @owner_assoc = association
81
+ end
82
+
83
+ def owner_assoc
84
+ @owner_assoc
85
+ end
86
+
87
+ def owner_class_sym
88
+ @owner_class_sym
89
+ end
90
+
91
+ def owner_class_key_sym
92
+ "#{owner_class_sym}_id".to_sym
93
+ end
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,7 @@
1
+ test:
2
+ adapter: mysql
3
+ encoding: utf8
4
+ database: property_sets_test
5
+ username: root
6
+ password:
7
+ socket: /tmp/mysql.sock
@@ -0,0 +1,14 @@
1
+ account1_foo:
2
+ account_id: 1
3
+ name: foo
4
+ value:
5
+
6
+ account1_bar:
7
+ account_id: 1
8
+ name: bar
9
+ value: 1
10
+
11
+ account1_baz:
12
+ account_id: 1
13
+ name: baz
14
+ value: 0