acts_as_preferenced 0.9.0
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/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README +75 -0
- data/Rakefile +1 -0
- data/lib/acts_as_preferenced.rb +72 -0
- data/lib/acts_as_preferenced/preference.rb +48 -0
- data/lib/acts_as_preferenced/section.rb +23 -0
- data/lib/acts_as_preferenced/store/association.rb +66 -0
- data/lib/acts_as_preferenced/store/base.rb +23 -0
- data/lib/acts_as_preferenced/store/field.rb +61 -0
- data/lib/acts_as_preferenced/version.rb +3 -0
- data/test/acts_as_preferences_test.rb +253 -0
- data/test/test_helper.rb +29 -0
- metadata +60 -0
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
ActsAsPreferenced
|
2
|
+
===========
|
3
|
+
|
4
|
+
ActsAsPreferenced helps with handling of some settings you might want to store
|
5
|
+
per-object (usually per-user) in Rails applications. It provides unified API to
|
6
|
+
access those, ways to handle default values and some useful view helpers.
|
7
|
+
Preferences may be divided into sections for easier categorization if many are
|
8
|
+
present.
|
9
|
+
|
10
|
+
Preferences can be stored in one of pluggable stores. Currently two stores are
|
11
|
+
implemented: Field (stores in serialized db column) and Association (stores in
|
12
|
+
associated table). One of the aims of this gem was to provide smooth transition
|
13
|
+
between different stores - API stays the same for all of them, so you may start
|
14
|
+
with preferences being stored in a serialized field and then transition to
|
15
|
+
associated table.
|
16
|
+
|
17
|
+
Example
|
18
|
+
=======
|
19
|
+
|
20
|
+
# Association store - use whatever association you like, there are just 2
|
21
|
+
# requirements:
|
22
|
+
# * associated table needs to have field for every preference (type matching)
|
23
|
+
# * the name of the association cannot be "preferences", as it is used
|
24
|
+
# internally by ActsAsPreferenced
|
25
|
+
|
26
|
+
class User
|
27
|
+
attr_accessor :user_preferences
|
28
|
+
|
29
|
+
acts_as_preferenced( :association => :user_preferences ) do |config|
|
30
|
+
|
31
|
+
# Just an integer preference with allowed range
|
32
|
+
config.preference :items_per_page, :section => :general, :choices => (1..100), :default => 20
|
33
|
+
# Define the whole section of preferences. :prefix => true means
|
34
|
+
# preference names will start with section name, so :flowers will become
|
35
|
+
# "likes_flowers?" method ("?" added because it's boolean)
|
36
|
+
config.section :likes, :prefix => true do |section|
|
37
|
+
section.preference :flowers, :default => true
|
38
|
+
# No default - if not set will be nil
|
39
|
+
section.preference :animals
|
40
|
+
end
|
41
|
+
# Enumerated preference with no section (it belongs to nil section)
|
42
|
+
config.preference :likes_people, :choices => [:no, :little, :yes]
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
OR
|
48
|
+
|
49
|
+
# Field store
|
50
|
+
|
51
|
+
class User
|
52
|
+
acts_as_preferenced( :field => :prefs, ...
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Usage
|
57
|
+
|
58
|
+
user = User.new
|
59
|
+
user.likes_flowers? # True - default; as preference is boolean question mark is appended to getter
|
60
|
+
user.likes_flowers = false
|
61
|
+
user.likes_flowers? # False
|
62
|
+
user.likes_animals? # Nil - no default present
|
63
|
+
user.items_per_page = 120 # ArgumentError - value out of range
|
64
|
+
user.likes_people = :yes
|
65
|
+
user.likes_people # :yes - name might be misleading, but it's not boolean, so no question mark here
|
66
|
+
|
67
|
+
TODO
|
68
|
+
====
|
69
|
+
|
70
|
+
Helpers
|
71
|
+
API documentation
|
72
|
+
Migration generator for Association store.
|
73
|
+
I18n tests.
|
74
|
+
|
75
|
+
Copyright (c) 2009-2013 Marek Janukowicz, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'acts_as_preferenced/version'
|
2
|
+
require 'acts_as_preferenced/store/base'
|
3
|
+
require 'acts_as_preferenced/store/association'
|
4
|
+
require 'acts_as_preferenced/store/field'
|
5
|
+
require 'acts_as_preferenced/preference'
|
6
|
+
require 'acts_as_preferenced/section'
|
7
|
+
|
8
|
+
module ActsAsPreferenced
|
9
|
+
|
10
|
+
def self.included( klass )
|
11
|
+
klass.extend( ActMethods )
|
12
|
+
end
|
13
|
+
|
14
|
+
module ActMethods
|
15
|
+
|
16
|
+
def acts_as_preferenced( options = {} )
|
17
|
+
options.assert_valid_keys( :field, :association, :translation_scope )
|
18
|
+
class_attribute :preference_config
|
19
|
+
include InstanceMethods
|
20
|
+
extend ClassMethods
|
21
|
+
if options[:field]
|
22
|
+
field = options[:field]
|
23
|
+
self.module_eval { serialize field, Hash } # TODO: move this line into config initialization
|
24
|
+
config = ActsAsPreferenced::Store::Field.new( self, field, options[:translation_scope] )
|
25
|
+
self.preference_config = config
|
26
|
+
yield config
|
27
|
+
elsif options[:association]
|
28
|
+
config = ActsAsPreferenced::Store::Association.new( self, options[:association], options[:translation_scope] )
|
29
|
+
self.preference_config = config
|
30
|
+
yield config
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Invalid options for acts_as_preferenced: #{options.inspect}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
|
40
|
+
# TODO: Those methods should only be available when preferences are actually defined
|
41
|
+
def preference_sections
|
42
|
+
preference_config.sections.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
def preferences_for_section( section )
|
46
|
+
preference_config.sections[section]
|
47
|
+
end
|
48
|
+
|
49
|
+
def all_preferences
|
50
|
+
preference_config.all_preferences
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
module InstanceMethods
|
56
|
+
|
57
|
+
def preference_value( name )
|
58
|
+
# TODO: implement
|
59
|
+
end
|
60
|
+
|
61
|
+
#def preferences
|
62
|
+
# Preferences.configs[self.class].all
|
63
|
+
#end
|
64
|
+
|
65
|
+
#def preferences= (prefs)
|
66
|
+
# Preferences.configs[self.class].all = prefs
|
67
|
+
#end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
ActiveRecord::Base.send( :include, ActsAsPreferenced )
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActsAsPreferenced
|
2
|
+
class Preference
|
3
|
+
|
4
|
+
attr_reader :name, :default, :section
|
5
|
+
|
6
|
+
def initialize( store, name, options = {} )
|
7
|
+
options.assert_valid_keys( :section, :label, :choices, :default )
|
8
|
+
@store, @name = store, name
|
9
|
+
@section = options[:section]
|
10
|
+
@label = options[:label]
|
11
|
+
@choices = options[:choices]
|
12
|
+
@default = options[:default]
|
13
|
+
store.sections[@section] << self
|
14
|
+
store.all_preferences << self
|
15
|
+
end
|
16
|
+
|
17
|
+
def label
|
18
|
+
if @store.translation_scope && @label.nil?
|
19
|
+
I18n.t( @name, :scope => [@store.translation_scope, @section] )
|
20
|
+
else
|
21
|
+
@label
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Options translations are stored under hardcoded :choices key in Preferences
|
26
|
+
# translation_scope. TODO: make it not hardcoded and make it work for other
|
27
|
+
# options than Array (eg. Range?)
|
28
|
+
# TODO: it should be cached
|
29
|
+
def choices
|
30
|
+
if @choices.is_a?( Array )
|
31
|
+
# TODO: I don't quite understand checking for an array here
|
32
|
+
@choices.collect { |choice| choice.is_a?( Array ) ? choice : [I18n.t( choice, :scope => [@store.translation_scope, :choices] ), opt] }
|
33
|
+
else
|
34
|
+
@choices
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def value_valid?( value )
|
39
|
+
value = value.to_sym if value.is_a?( String )
|
40
|
+
if @choices
|
41
|
+
return @choices.include?( value )
|
42
|
+
else
|
43
|
+
return [true, false].include?( value )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActsAsPreferenced
|
2
|
+
|
3
|
+
# This class only helps with defining of preference sections, not with storage
|
4
|
+
# (as I see no reason to make storage more complicated while bringing no real
|
5
|
+
# benefit)
|
6
|
+
class Section
|
7
|
+
|
8
|
+
# Options:
|
9
|
+
# * prefix - if true, section name will be prepended to all preferences
|
10
|
+
# defined within. False by default
|
11
|
+
def initialize( store, name, options = {} )
|
12
|
+
options.assert_valid_keys( :prefix )
|
13
|
+
@store, @name = store, name
|
14
|
+
@prefix = options[:prefix]
|
15
|
+
end
|
16
|
+
|
17
|
+
def preference( name, options = {} )
|
18
|
+
pref_name = (@prefix ? "#{@name}_#{name}".to_sym : name)
|
19
|
+
@store.preference( pref_name, options.merge( :section => @name ))
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActsAsPreferenced
|
2
|
+
module Store
|
3
|
+
|
4
|
+
class Association < Base
|
5
|
+
|
6
|
+
attr_reader :association
|
7
|
+
|
8
|
+
def initialize(klass, assoc, translation_scope = nil)
|
9
|
+
super( klass, translation_scope )
|
10
|
+
@association = assoc
|
11
|
+
@klass.send( :define_method, "preferences" ) do
|
12
|
+
prefs = send( preference_config.association ) || send( "build_#{preference_config.association}" )
|
13
|
+
return prefs
|
14
|
+
end
|
15
|
+
@klass.send( :define_method, "preferences=" ) do |new_prefs|
|
16
|
+
pref_object = send( preference_config.association ) || send( "build_#{preference_config.association}" )
|
17
|
+
self.class.all_preferences.each do |pref|
|
18
|
+
old_value = pref_object.send( pref.name )
|
19
|
+
new_value = new_prefs[pref.name]
|
20
|
+
if new_value
|
21
|
+
stringified_value = new_value.is_a?( Symbol ) ? new_value.to_s : new_value
|
22
|
+
new_prefs[pref.name] = stringified_value
|
23
|
+
else
|
24
|
+
# Hack to allow boolean preferences to be set to false. This looks
|
25
|
+
# wrong from model point of view (if you mass assign preferences,
|
26
|
+
# absent boolean ones will be set to false even though default is
|
27
|
+
# true), but it works well for forms.
|
28
|
+
# TODO: find a way to make it work properly both for defaults and
|
29
|
+
# forms (if at all possible)
|
30
|
+
if pref.default == true || old_value == true
|
31
|
+
new_prefs[pref.name] = false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
pref_object.attributes = new_prefs
|
36
|
+
end
|
37
|
+
|
38
|
+
@klass.send( :define_method, "save_preferences" ) do
|
39
|
+
pref_object = send( preference_config.association )
|
40
|
+
pref_object.save! if pref_object
|
41
|
+
end
|
42
|
+
@klass.before_save :save_preferences
|
43
|
+
end
|
44
|
+
|
45
|
+
def preference( name, options = {} )
|
46
|
+
options.assert_valid_keys( :section, :label, :choices, :default )
|
47
|
+
pref = Preference.new( self, name, options )
|
48
|
+
method_name = options[:choices] ? name.to_s : "#{name}?"
|
49
|
+
@klass.send( :define_method, method_name ) do
|
50
|
+
value = preferences[name]
|
51
|
+
value = pref.default if value.nil?
|
52
|
+
value &&= value.to_sym if value.is_a?( String )
|
53
|
+
value
|
54
|
+
end
|
55
|
+
@klass.send( :define_method, "#{name}=" ) do |value|
|
56
|
+
if pref.value_valid?( value )
|
57
|
+
preferences[name] = (value.is_a?( Symbol ) ? value.to_s : value)
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Value #{value} invalid for preference #{pref.name}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActsAsPreferenced
|
2
|
+
module Store
|
3
|
+
|
4
|
+
# Base class for all stores for preferences
|
5
|
+
class Base
|
6
|
+
|
7
|
+
attr_reader :sections, :all_preferences, :translation_scope
|
8
|
+
|
9
|
+
def initialize( klass, translation_scope = nil )
|
10
|
+
@klass = klass
|
11
|
+
@translation_scope = translation_scope
|
12
|
+
@sections = Hash.new { |h,k| h[k] = [] }
|
13
|
+
@all_preferences = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def section( name, options = {} )
|
17
|
+
section = Section.new( self, name, options )
|
18
|
+
yield section
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ActsAsPreferenced
|
2
|
+
module Store
|
3
|
+
|
4
|
+
# This store holds preferences in a serialized field
|
5
|
+
class Field < Base
|
6
|
+
|
7
|
+
def initialize( klass, field, translation_scope = nil )
|
8
|
+
super( klass, translation_scope )
|
9
|
+
@field = field
|
10
|
+
@klass.send( :define_method, "preferences" ) do
|
11
|
+
prefs = read_attribute(field)
|
12
|
+
unless prefs
|
13
|
+
prefs = HashWithIndifferentAccess.new
|
14
|
+
write_attribute(field, prefs)
|
15
|
+
end
|
16
|
+
return prefs
|
17
|
+
end
|
18
|
+
|
19
|
+
@klass.send( :define_method, "preferences=" ) do |new_prefs|
|
20
|
+
self.class.all_preferences.each do |pref|
|
21
|
+
# Hack to allow boolean preferences to be set to false. This looks
|
22
|
+
# wrong from model point of view (if you mass assign preferences,
|
23
|
+
# absent boolean ones will be set to false even though default is
|
24
|
+
# true), but it works well for forms.
|
25
|
+
# TODO: find a way to make it work properly both for defaults and
|
26
|
+
# forms (if at all possible)
|
27
|
+
new_prefs[pref.name] ||= false if pref.default == true
|
28
|
+
end
|
29
|
+
write_attribute(field, new_prefs)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def preference( name, options = {} )
|
34
|
+
options.assert_valid_keys( :section, :label, :choices, :default )
|
35
|
+
pref = Preference.new( self, name, options )
|
36
|
+
if options[:choices] # Choice
|
37
|
+
@klass.send( :define_method, "#{name}" ) do
|
38
|
+
# This symbolizes strings and leave other values (eg. numbers)
|
39
|
+
# intact
|
40
|
+
value = preferences[name].is_a?( String ) ? preferences[name].to_sym : preferences[name]
|
41
|
+
value || pref.default
|
42
|
+
end
|
43
|
+
else # Boolean
|
44
|
+
@klass.send( :define_method, "#{name}?" ) do
|
45
|
+
# We check for nil to allow "false" here
|
46
|
+
preferences[name].nil? ? pref.default : preferences[name]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@klass.send( :define_method, "#{name}=" ) do |value|
|
50
|
+
if pref.value_valid?( value )
|
51
|
+
preferences[name] = value
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Value #{value} invalid for preference #{pref.name}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ActsAsPreferencedFieldTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
class User < FakeModel
|
6
|
+
|
7
|
+
attr_accessor :hold_preferences_here
|
8
|
+
|
9
|
+
acts_as_preferenced( :field => :hold_preferences_here ) do |config|
|
10
|
+
config.preference :items_per_page, :section => :general, :choices => (1..100), :default => 20
|
11
|
+
config.section :likes, :prefix => true do |section|
|
12
|
+
section.preference :flowers, :default => true
|
13
|
+
section.preference :animals
|
14
|
+
end
|
15
|
+
config.preference :likes_people, :choices => [:no, :little, :yes]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup
|
20
|
+
@user = User.new
|
21
|
+
end
|
22
|
+
|
23
|
+
test "all preferences" do
|
24
|
+
all = User.all_preferences
|
25
|
+
assert_equal [:items_per_page, :likes_flowers, :likes_animals, :likes_people], all.collect(&:name)
|
26
|
+
end
|
27
|
+
|
28
|
+
test "preference sections" do
|
29
|
+
assert_equal [:general, nil, :likes], User.preference_sections
|
30
|
+
prefs = User.preferences_for_section( :sdjkfajsdf )
|
31
|
+
assert prefs.empty?
|
32
|
+
prefs = User.preferences_for_section( :likes )
|
33
|
+
assert_equal [:likes_flowers, :likes_animals], prefs.collect(&:name)
|
34
|
+
prefs = User.preferences_for_section( nil )
|
35
|
+
assert_equal [:likes_people], prefs.collect(&:name)
|
36
|
+
end
|
37
|
+
|
38
|
+
test "defaults" do
|
39
|
+
assert_equal true, @user.likes_flowers?
|
40
|
+
assert_equal 20, @user.items_per_page
|
41
|
+
assert_nil @user.likes_animals?
|
42
|
+
assert_nil @user.likes_people
|
43
|
+
end
|
44
|
+
|
45
|
+
test "set boolean" do
|
46
|
+
@user.likes_flowers = false
|
47
|
+
assert_equal false, @user.likes_flowers?
|
48
|
+
end
|
49
|
+
|
50
|
+
test "set integer" do
|
51
|
+
@user.items_per_page = 11
|
52
|
+
assert_equal 11, @user.items_per_page
|
53
|
+
end
|
54
|
+
|
55
|
+
test "set enumerable" do
|
56
|
+
@user.likes_people = :little
|
57
|
+
assert_equal :little, @user.likes_people
|
58
|
+
end
|
59
|
+
|
60
|
+
test "set integer as boolean" do
|
61
|
+
assert_raises( ArgumentError ) do
|
62
|
+
@user.items_per_page = true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
test "set integer out of range" do
|
67
|
+
assert_raises( ArgumentError ) do
|
68
|
+
@user.items_per_page = 0
|
69
|
+
end
|
70
|
+
assert_raises( ArgumentError ) do
|
71
|
+
@user.items_per_page = 101
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
test "set boolean as integer" do
|
76
|
+
assert_raises( ArgumentError ) do
|
77
|
+
@user.likes_flowers = 10
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
test "set enumerable out of range" do
|
82
|
+
assert_raises( ArgumentError ) do
|
83
|
+
@user.likes_people = :lot
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
test "set enumerable as string" do
|
88
|
+
@user.likes_people = "yes"
|
89
|
+
assert_equal :yes, @user.likes_people
|
90
|
+
end
|
91
|
+
|
92
|
+
test "set batch preferences" do
|
93
|
+
@user.likes_flowers = true
|
94
|
+
@user.likes_animals = true
|
95
|
+
@user.preferences = { :items_per_page => 23 }
|
96
|
+
assert_equal 23, @user.items_per_page
|
97
|
+
# No value for boolean defaulting to true - we assume it was unset
|
98
|
+
# TODO: in fact Rails handle this by sending hidden field with the same
|
99
|
+
# name, use the same approach
|
100
|
+
assert_equal false, @user.likes_flowers?
|
101
|
+
# No value for this boolean without default - reset it
|
102
|
+
# TODO: this also doesn't seem correct
|
103
|
+
assert_nil @user.likes_animals?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class ActsAsPreferencedAssociationTest < ActiveSupport::TestCase
|
108
|
+
|
109
|
+
class UserPreference < FakeModel
|
110
|
+
|
111
|
+
# Tons of fakes here
|
112
|
+
attr_accessor :attributes
|
113
|
+
|
114
|
+
def items_per_page
|
115
|
+
attributes[:items_per_page]
|
116
|
+
end
|
117
|
+
|
118
|
+
def items_per_page= (value)
|
119
|
+
attributes[:items_per_page] = value
|
120
|
+
end
|
121
|
+
|
122
|
+
def likes_flowers?
|
123
|
+
attributes[:likes_flowers]
|
124
|
+
end
|
125
|
+
|
126
|
+
def likes_flowers= (value)
|
127
|
+
attributes[:likes_flowers] = value
|
128
|
+
end
|
129
|
+
|
130
|
+
def likes_animals?
|
131
|
+
attributes[:likes_animals]
|
132
|
+
end
|
133
|
+
|
134
|
+
def likes_animals= (value)
|
135
|
+
attributes[:likes_animals] = value
|
136
|
+
end
|
137
|
+
|
138
|
+
def likes_people?
|
139
|
+
attributes[:likes_people]
|
140
|
+
end
|
141
|
+
|
142
|
+
def likes_people= (value)
|
143
|
+
attributes[:likes_people] = value
|
144
|
+
end
|
145
|
+
|
146
|
+
alias_method :likes_flowers, :likes_flowers?
|
147
|
+
alias_method :likes_animals, :likes_animals?
|
148
|
+
alias_method :likes_people, :likes_people?
|
149
|
+
delegate :[], :[]=, :to => :attributes
|
150
|
+
end
|
151
|
+
|
152
|
+
class User < FakeModel
|
153
|
+
|
154
|
+
attr_accessor :user_preferences
|
155
|
+
|
156
|
+
acts_as_preferenced( :association => :user_preferences ) do |config|
|
157
|
+
config.preference :items_per_page, :section => :general, :choices => (1..100), :default => 20
|
158
|
+
config.section :likes, :prefix => true do |section|
|
159
|
+
section.preference :flowers, :default => true
|
160
|
+
section.preference :animals
|
161
|
+
end
|
162
|
+
config.preference :likes_people, :choices => [:no, :little, :yes]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def setup
|
167
|
+
@user = User.new
|
168
|
+
@user.user_preferences = UserPreference.new
|
169
|
+
end
|
170
|
+
|
171
|
+
test "all preferences" do
|
172
|
+
all = User.all_preferences
|
173
|
+
assert_equal [:items_per_page, :likes_flowers, :likes_animals, :likes_people], all.collect(&:name)
|
174
|
+
end
|
175
|
+
|
176
|
+
test "preference sections" do
|
177
|
+
assert_equal [:general, nil, :likes], User.preference_sections
|
178
|
+
prefs = User.preferences_for_section( :sdjkfajsdf )
|
179
|
+
assert prefs.empty?
|
180
|
+
prefs = User.preferences_for_section( :likes )
|
181
|
+
assert_equal [:likes_flowers, :likes_animals], prefs.collect(&:name)
|
182
|
+
prefs = User.preferences_for_section( nil )
|
183
|
+
assert_equal [:likes_people], prefs.collect(&:name)
|
184
|
+
end
|
185
|
+
|
186
|
+
test "defaults" do
|
187
|
+
assert_equal true, @user.likes_flowers?
|
188
|
+
assert_equal 20, @user.items_per_page
|
189
|
+
assert_nil @user.likes_animals?
|
190
|
+
assert_nil @user.likes_people
|
191
|
+
end
|
192
|
+
|
193
|
+
test "set boolean" do
|
194
|
+
@user.likes_flowers = false
|
195
|
+
assert_equal false, @user.likes_flowers?
|
196
|
+
end
|
197
|
+
|
198
|
+
test "set integer" do
|
199
|
+
@user.items_per_page = 11
|
200
|
+
assert_equal 11, @user.items_per_page
|
201
|
+
end
|
202
|
+
|
203
|
+
test "set enumerable" do
|
204
|
+
@user.likes_people = :little
|
205
|
+
assert_equal :little, @user.likes_people
|
206
|
+
end
|
207
|
+
|
208
|
+
test "set integer as boolean" do
|
209
|
+
assert_raises( ArgumentError ) do
|
210
|
+
@user.items_per_page = true
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
test "set integer out of range" do
|
215
|
+
assert_raises( ArgumentError ) do
|
216
|
+
@user.items_per_page = 0
|
217
|
+
end
|
218
|
+
assert_raises( ArgumentError ) do
|
219
|
+
@user.items_per_page = 101
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
test "set boolean as integer" do
|
224
|
+
assert_raises( ArgumentError ) do
|
225
|
+
@user.likes_flowers = 10
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
test "set enumerable out of range" do
|
230
|
+
assert_raises( ArgumentError ) do
|
231
|
+
@user.likes_people = :lot
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
test "set enumerable as string" do
|
236
|
+
@user.likes_people = "yes"
|
237
|
+
assert_equal :yes, @user.likes_people
|
238
|
+
end
|
239
|
+
|
240
|
+
test "set batch preferences" do
|
241
|
+
@user.likes_flowers = true
|
242
|
+
@user.likes_animals = false
|
243
|
+
@user.preferences = { :items_per_page => 23 }
|
244
|
+
assert_equal 23, @user.items_per_page
|
245
|
+
# No value for boolean defaulting to true - we assume it was unset
|
246
|
+
# TODO: in fact Rails handle this by sending hidden field with the same
|
247
|
+
# name, use the same approach
|
248
|
+
assert_equal false, @user.likes_flowers?
|
249
|
+
# No value for this boolean without default - reset it
|
250
|
+
# TODO: this also doesn't seem correct
|
251
|
+
assert_nil @user.likes_animals?
|
252
|
+
end
|
253
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'set'
|
4
|
+
gem 'activesupport', "> 3.0.0"
|
5
|
+
require 'active_support/test_case'
|
6
|
+
require 'active_record'
|
7
|
+
require 'active_record/base'
|
8
|
+
require 'acts_as_preferenced'
|
9
|
+
require 'acts_as_preferenced/preference'
|
10
|
+
require 'acts_as_preferenced/section'
|
11
|
+
require 'acts_as_preferenced/store/base'
|
12
|
+
require 'acts_as_preferenced/store/association'
|
13
|
+
require 'acts_as_preferenced/store/field'
|
14
|
+
|
15
|
+
# Class faking AR model behavior - needed to test the validations without the database
|
16
|
+
class FakeModel < ActiveRecord::Base
|
17
|
+
|
18
|
+
self.abstract_class = true
|
19
|
+
|
20
|
+
def self.columns
|
21
|
+
@columns ||= [];
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.column(name, sql_type = nil, default = nil, null = true)
|
25
|
+
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_preferenced
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marek Janukowicz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-07 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Preferences for Rails
|
15
|
+
email:
|
16
|
+
- marek@janukowicz.net
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/acts_as_preferenced.rb
|
22
|
+
- lib/acts_as_preferenced/version.rb
|
23
|
+
- lib/acts_as_preferenced/section.rb
|
24
|
+
- lib/acts_as_preferenced/store/field.rb
|
25
|
+
- lib/acts_as_preferenced/store/association.rb
|
26
|
+
- lib/acts_as_preferenced/store/base.rb
|
27
|
+
- lib/acts_as_preferenced/preference.rb
|
28
|
+
- Gemfile
|
29
|
+
- LICENSE.txt
|
30
|
+
- Rakefile
|
31
|
+
- README
|
32
|
+
- test/acts_as_preferences_test.rb
|
33
|
+
- test/test_helper.rb
|
34
|
+
homepage: ''
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.24
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: ''
|
58
|
+
test_files:
|
59
|
+
- test/acts_as_preferences_test.rb
|
60
|
+
- test/test_helper.rb
|