preferable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,117 @@
1
+ # Preferable
2
+
3
+ Simple filtering for ActiveRecord. Sanitizes simple and readable query parameters -great for building APIs & HTML filters.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your Gemfile:
8
+
9
+ gem "preferable"
10
+
11
+ Then, bundle:
12
+
13
+ $ bundle install
14
+
15
+ Add a new migration to your project. Example:
16
+
17
+ $ rails g migration AddPreferencesToUsers
18
+
19
+ # Rails 2 & 3.0
20
+ class AddPreferencesToUsers < ActiveRecord::Migration
21
+ def self.up
22
+ add_column :users, :prefeences, :text
23
+ end
24
+
25
+ def self.down
26
+ remove_column :users, :prefeences, :text
27
+ end
28
+ end
29
+
30
+ # Rails >=3.1
31
+ class AddPreferencesToUsers < ActiveRecord::Migration
32
+ def change
33
+ add_column :users, :prefeences, :text
34
+ end
35
+ end
36
+
37
+
38
+ ## Usage Examples:
39
+
40
+ Specify simple preferences, with defaults:
41
+
42
+ class User < ActiveRecord::Base
43
+
44
+ preferable do
45
+ integer :theme_id
46
+ boolean :newsletter, :default => false
47
+ string :font_color, :default => "444444"
48
+ end
49
+
50
+ end
51
+
52
+ Read and write preferences:
53
+
54
+ user = User.find(1)
55
+ user.preferences[:newsletter] # => false
56
+
57
+ # Set single preferences (with type casting)
58
+ user.preferences[:newsletter] = '1'
59
+ user.preferences[:newsletter] # => true
60
+
61
+ # or, bulk-set (e.g. from forms)
62
+ user.preferences = { :newsletter => '1' }
63
+
64
+ # Makes preferences persistent
65
+ user.save
66
+
67
+ Specify condititions (if/unless):
68
+
69
+ class User < ActiveRecord::Base
70
+
71
+ preferable do
72
+ string :font_color, :default => "444444", :if => lambda {|v| v =~ /^[A-F0-9]{6}$/ }
73
+ end
74
+
75
+ end
76
+
77
+ user = User.find(1)
78
+ user.preferences[:font_color] = 'INVALID'
79
+ user.preferences[:font_color] # => '444444'
80
+
81
+ Store even arrays (with internal casting):
82
+
83
+ class User < ActiveRecord::Base
84
+
85
+ preferable do
86
+ array :popular_words, :cast => :string
87
+ end
88
+
89
+ end
90
+
91
+ user = User.find(1)
92
+ user.preferences[:popular_words] = 'hello'
93
+ user.preferences[:popular_words] # => ['hello']
94
+
95
+ ## License
96
+
97
+ Copyright (C) 2011 Dimitrij Denissenko
98
+
99
+ Permission is hereby granted, free of charge, to any person obtaining
100
+ a copy of this software and associated documentation files (the
101
+ "Software"), to deal in the Software without restriction, including
102
+ without limitation the rights to use, copy, modify, merge, publish,
103
+ distribute, sublicense, and/or sell copies of the Software, and to
104
+ permit persons to whom the Software is furnished to do so, subject to
105
+ the following conditions:
106
+
107
+ The above copyright notice and this permission notice shall be
108
+ included in all copies or substantial portions of the Software.
109
+
110
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
111
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
112
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
113
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
114
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
115
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
116
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
117
+
@@ -0,0 +1,92 @@
1
+ class Preferable::Field
2
+ TYPES = [:string, :integer, :float, :boolean, :date, :datetime, :array].to_set.freeze
3
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
4
+
5
+ attr_reader :name, :type, :options, :default
6
+
7
+ # Create a new Field.
8
+ #
9
+ # Params:
10
+ # name: The name symbol
11
+ # type: The type symbol, see Preferable::Field::TYPES
12
+ # options: The options hash (keys as symbols)
13
+ #
14
+ # Options:
15
+ # default: The default value.
16
+ # if: Value assignment proc. Value is only assigned if true is yielded.
17
+ # unless: Value assignment proc. Value is only assigned if false is yielded.
18
+ # cast: Only relevant for :array type. Specify the type of the array contents.
19
+ #
20
+ # Examples:
21
+ # Field.new :color, :string, :if => lambda {|v| v =~ /^[A-F0-9]{6}$/ }
22
+ # Field.new :holidays, :array, :cast => :date
23
+ # Field.new :newsletter, :boolean, :default => false
24
+ # Field.new :age, :integer, :unless => &:zero?
25
+ #
26
+ def initialize(name, type, options = {})
27
+ raise ArgumentError, "Unknown type '#{type}', available types are: #{TYPES.map(&:to_s).join(', ')}" unless TYPES.include?(type.to_sym)
28
+
29
+ @name = name.to_sym
30
+ @type = type.to_sym
31
+ @options = options.dup
32
+ @default = type_cast @options.delete(:default)
33
+ end
34
+
35
+ # Returns true if a value is assignable, else false. Assumes given value is already type-casted.
36
+ def valid?(value)
37
+ result = true
38
+ result = options[:if].call(value) if result && options[:if]
39
+ result = !options[:unless].call(value) if result && options[:unless]
40
+ result
41
+ end
42
+
43
+ # Opposite of #valid?
44
+ def invalid?(value)
45
+ !valid?(value)
46
+ end
47
+
48
+ # Is value equal to the default. . Assumes given value is already type-casted.
49
+ def default?(value)
50
+ value == default
51
+ end
52
+
53
+ # Converts a value.
54
+ def type_cast(value, to = self.type)
55
+ return nil unless value
56
+
57
+ case to
58
+ when :string
59
+ value.to_s
60
+ when :integer
61
+ value.to_i
62
+ when :boolean
63
+ TRUE_VALUES.include?(value)
64
+ when :datetime
65
+ to_time(value)
66
+ when :date
67
+ to_time(value).try(:to_date)
68
+ when :float
69
+ value.to_f
70
+ when :array
71
+ Array.wrap(value).tap do |wrap|
72
+ wrap.map! {|item| type_cast(item, options[:cast]) } if options[:cast]
73
+ end
74
+ else
75
+ value
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def to_time(value)
82
+ case value
83
+ when Time
84
+ value.in_time_zone
85
+ when Date
86
+ value.beginning_of_day
87
+ else
88
+ Time.zone.parse(value) rescue nil
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,55 @@
1
+ # Includable module, for ActiveRecord::Base
2
+ module Preferable::Model
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :_preferable
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ # Preferable definition for a model. Example:
12
+ #
13
+ # class User < ActiveRecord::Base
14
+ #
15
+ # preferable do
16
+ # integer :theme_id
17
+ # boolean :accessible, :default => false
18
+ # string :font_color, :default => "444444", :if => lambda {|value| value =~ /^[A-F0-9]{6}$/ }
19
+ # end
20
+ #
21
+ # end
22
+ #
23
+ def preferable(&block)
24
+ unless _preferable
25
+ self._preferable = Preferable::Schema.new
26
+ serialize :preferences, Preferable::Set
27
+ include PreferableMethods
28
+ end
29
+ self._preferable.instance_eval(&block) if block
30
+ self._preferable
31
+ end
32
+
33
+ end
34
+
35
+ module PreferableMethods
36
+
37
+ # Accessor to preferences. Examples:
38
+ #
39
+ # user = User.find(1)
40
+ # user.preferences[:theme_id] # => 8
41
+ # user.preferences[:theme_id] = 3
42
+ #
43
+ def preferences
44
+ value = read_attribute(:preferences)
45
+ value.is_a?(Preferable::Set) ? value : write_attribute(:preferences, Preferable::Set.new(self.class.name))
46
+ end
47
+
48
+ # Preferences writer. Updates existing preferences (doesn't replace them!)
49
+ def preferences=(hash)
50
+ preferences.set(hash) if hash.is_a?(Hash)
51
+ preferences
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,20 @@
1
+ require 'set'
2
+
3
+ class Preferable::Schema < Hash
4
+
5
+ def initialize
6
+ super
7
+ end
8
+
9
+ def field(name, type, options = {})
10
+ item = Preferable::Field.new(name, type, options)
11
+ self[item.name] = item
12
+ end
13
+
14
+ Preferable::Field::TYPES.each do |sym|
15
+ define_method sym do |name, options|
16
+ field name, sym, options
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,46 @@
1
+ class Preferable::Set < Hash
2
+ MODEL_KEY = "_".freeze
3
+ PRIVATE = [:rehash, :fetch, :store, :shift, :delete, :delete_if, :keep_if].to_set
4
+
5
+ # Make destructive methods private
6
+ (public_instance_methods - Hash.superclass.public_instance_methods).each do |m|
7
+ PRIVATE.include?(m.to_sym) || m.to_s.ends_with?('!')
8
+ end
9
+
10
+ def initialize(model_name)
11
+ super()
12
+ store MODEL_KEY, "::#{model_name}"
13
+ end
14
+
15
+ def model
16
+ @model ||= fetch(MODEL_KEY).constantize
17
+ end
18
+
19
+ def [](name)
20
+ field = find_field(name)
21
+ super(field.name) || field.default if field
22
+ end
23
+
24
+ def []=(name, value)
25
+ field = find_field(name) || return
26
+ value = field.type_cast(value)
27
+
28
+ if value.nil? || field.invalid?(value) || field.default?(value)
29
+ delete field.name
30
+ else
31
+ super field.name, value
32
+ end
33
+ end
34
+
35
+ def set(pairs)
36
+ pairs.each {|k, v| self[k] = v }
37
+ self
38
+ end
39
+
40
+ private
41
+
42
+ def find_field(name)
43
+ model.preferable[name.to_sym]
44
+ end
45
+
46
+ end
data/lib/preferable.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "active_support/core_ext"
2
+ require "active_record"
3
+
4
+ module Preferable
5
+ autoload :Model, "preferable/model"
6
+ autoload :Schema, "preferable/schema"
7
+ autoload :Field, "preferable/field"
8
+ autoload :Set, "preferable/set"
9
+ end
10
+
11
+ ActiveRecord::Base.class_eval do
12
+ include ::Preferable::Model
13
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: preferable
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Dimitrij Denissenko
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-07 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: abstract
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 7
44
+ segments:
45
+ - 3
46
+ - 0
47
+ - 0
48
+ version: 3.0.0
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: activesupport
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 7
60
+ segments:
61
+ - 3
62
+ - 0
63
+ - 0
64
+ version: 3.0.0
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ description: Great for storing user preferences
68
+ email: dimitrij@blacksquaremedia.com
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files: []
74
+
75
+ files:
76
+ - README.markdown
77
+ - lib/preferable/field.rb
78
+ - lib/preferable/set.rb
79
+ - lib/preferable/schema.rb
80
+ - lib/preferable/model.rb
81
+ - lib/preferable.rb
82
+ has_rdoc: true
83
+ homepage: https://github.com/bsm/preferable
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options: []
88
+
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 57
97
+ segments:
98
+ - 1
99
+ - 8
100
+ - 7
101
+ version: 1.8.7
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 23
108
+ segments:
109
+ - 1
110
+ - 3
111
+ - 6
112
+ version: 1.3.6
113
+ requirements: []
114
+
115
+ rubyforge_project:
116
+ rubygems_version: 1.6.2
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: Simple preferences store for ActiveRecord
120
+ test_files: []
121
+