has_preference 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Thomas Boerger <tboerger@tbpro.de>
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.
data/README ADDED
@@ -0,0 +1,15 @@
1
+ # has_preference
2
+
3
+ Add preference functionality to your ActiveRecord models.
4
+
5
+ ## Install
6
+
7
+ gem 'has_preference'
8
+
9
+ ## Usage
10
+
11
+
12
+
13
+ ## Credits
14
+
15
+ The project <tt>has_preference</tt> have been inspired by pluginaweek. Take a look at the git [repositories](https://github.com/pluginaweek).
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ # Created by Thomas Boerger on 2011-07-01.
2
+ # Copyright (c) 2011. All rights reserved.
3
+
4
+ require 'rubygems'
5
+ require 'rubygems/package_task'
6
+ require 'rubygems/specification'
7
+ require 'rake'
8
+ require 'rake/testtask'
9
+ require 'rdoc/task'
10
+ require 'rcov/rcovtask'
11
+ require 'date'
12
+
13
+ GEM_TITLE = 'has_preference'
14
+ GEM_VERSION = '0.0.1'
15
+
16
+ spec = Gem::Specification.new do |s|
17
+ s.name = GEM_TITLE
18
+ s.version = GEM_VERSION
19
+ s.platform = Gem::Platform::RUBY
20
+ s.extra_rdoc_files = ['README', 'LICENSE']
21
+ s.rdoc_options << '--line-numbers' << '--main=README'
22
+ s.summary = 'Has preference'
23
+ s.description = 'Preference for ActiveRecord models'
24
+ s.author = 'Thomas Boerger'
25
+ s.email = 'tboerger@tbpro.de'
26
+ s.homepage = 'http://github.com/tbpro/has_preference'
27
+ s.files = %w(LICENSE README Rakefile init.rb) + Dir.glob('{app,generators,lib,test}/**/*')
28
+ s.test_files = Dir["test/**/*_test.rb"]
29
+ end
30
+
31
+ Gem::PackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ desc 'Create a gemspec'
36
+ task :writespec do
37
+ File.open("#{GEM_TITLE}.gemspec", "w") do |file|
38
+ file.puts spec.to_ruby
39
+ end
40
+ end
41
+
42
+ desc 'Docs for gem'
43
+ Rake::RDocTask.new(:rdoc) do |rdoc|
44
+ rdoc.rdoc_dir = 'doc'
45
+ rdoc.title = spec.name
46
+ rdoc.options << '--line-numbers' << '--main=README'
47
+ rdoc.rdoc_files.include('README', 'LICENSE', 'lib/**/*.rb', 'app/**/*.rb')
48
+ end
49
+
50
+ #desc 'Tests for gem'
51
+ #Rake::TestTask.new(:test) do |t|
52
+ # t.libs << 'lib'
53
+ # t.test_files = spec.test_files
54
+ # t.verbose = true
55
+ #end
56
+
57
+ #desc 'Rcov for gem'
58
+ #Rcov::RcovTask.new(:rcov) do |t|
59
+ # t.libs << 'lib'
60
+ # t.test_files = spec.test_files
61
+ # t.rcov_opts << '--exclude="^(?!lib/|app/)"'
62
+ # t.verbose = true
63
+ #end
64
+
65
+ desc 'List all tasks'
66
+ task :default do
67
+ puts `rake -T`.grep(/^[^(].*$/)
68
+ end
@@ -0,0 +1,41 @@
1
+ class Preference < ActiveRecord::Base
2
+ belongs_to :owner, :polymorphic => true
3
+ belongs_to :group, :polymorphic => true
4
+
5
+ validates_presence_of :name, :owner_id, :owner_type
6
+ validates_presence_of :group_type, :if => :group_id?
7
+
8
+ def definition
9
+ owner_type && (find_definition(owner_type.constantize) || find_definition(owner.class))
10
+ end
11
+
12
+ def value
13
+ value = read_attribute(:value)
14
+ value = definition.type_cast(value) if definition
15
+
16
+ value
17
+ end
18
+
19
+ def group_with_optional_lookup
20
+ group_id ? group_without_optional_lookup : group_type
21
+ end
22
+
23
+ alias_method_chain :group, :optional_lookup
24
+
25
+ class << self
26
+ def split_group(group = nil)
27
+ if group.is_a?(ActiveRecord::Base)
28
+ group_id, group_type = group.id, group.class.base_class.name.to_s
29
+ else
30
+ group_id, group_type = nil, group.is_a?(Symbol) ? group.to_s : group
31
+ end
32
+
33
+ [group_id, group_type]
34
+ end
35
+ end
36
+
37
+ protected
38
+ def find_definition(owner_class)
39
+ owner_class.respond_to?(:preference_definitions) && owner_class.preference_definitions[name]
40
+ end
41
+ end
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ # Created by Thomas Boerger on 2011-07-01.
2
+ # Copyright (c) 2011. All rights reserved.
3
+
4
+ require "has_preference"
@@ -0,0 +1,11 @@
1
+ module HasAttribute
2
+ module Generators
3
+ class MigrationGenerator < Rails::Generators::Base
4
+ desc "Builds the migration for preferences table"
5
+
6
+ def create_migration_file
7
+ migration_template "migration.rb", "db/migrate/create_preferences.rb"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class CreatePreferences < ActiveRecord::Migration
2
+ def change
3
+ create_table :preferences do |t|
4
+ t.string :name, :null => false
5
+ t.references :owner, :polymorphic => true, :null => false
6
+ t.references :group, :polymorphic => true
7
+ t.string :value
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :preferences, [:owner_id, :owner_type, :name, :group_id, :group_type], :unique => true, :name => 'preferences_owner_id_owner_type_name_group_id_group_type'
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ require 'rails/generators/named_base'
2
+ require 'rails/generators/migration'
3
+ require 'rails/generators/active_model'
4
+
5
+ require 'generators/helpers/migration_helper'
6
+
7
+ module HasPreference
8
+ module Generators
9
+ class Base < Rails::Generators::Base
10
+ include Rails3Generators::MigrationHelper
11
+ #include Rails::Generators::Migration
12
+
13
+ def self.source_root
14
+ @_has_preference_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'has_preference', generator_name, 'templates'))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require 'generators/has_preference'
2
+
3
+ module HasPreference
4
+ module Generators
5
+ class MigrationGenerator < Base
6
+ desc "Builds the migration for preferences table"
7
+
8
+ def create_migration_file
9
+ migration_template "migration.rb", "db/migrate/create_preferences.rb"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class CreatePreferences < ActiveRecord::Migration
2
+ def change
3
+ create_table :preferences do |t|
4
+ t.string :name, :null => false
5
+ t.references :owner, :polymorphic => true, :null => false
6
+ t.references :group, :polymorphic => true
7
+ t.string :value
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :preferences, [:owner_id, :owner_type, :name, :group_id, :group_type], :unique => true, :name => 'preferences_owner_id_owner_type_name_group_id_group_type'
12
+ end
13
+ end
@@ -0,0 +1,361 @@
1
+ # Created by Thomas Boerger on 2011-07-01.
2
+ # Copyright (c) 2011. All rights reserved.
3
+
4
+ module HasPreference
5
+ module MacroMethods
6
+ def has_preference(name, *args)
7
+ unless included_modules.include?(InstanceMethods)
8
+ class_inheritable_hash :preference_definitions
9
+ self.preference_definitions = {}
10
+
11
+ has_many :stored_preferences, :as => :owner, :class_name => 'Preference'
12
+
13
+ after_save :update_preferences
14
+
15
+ named_scope :with_preferences, lambda {|preferences| build_preference_scope(preferences)}
16
+ named_scope :without_preferences, lambda {|preferences| build_preference_scope(preferences, true)}
17
+
18
+ extend HasPreference::ClassMethods
19
+ include HasPreference::InstanceMethods
20
+ end
21
+
22
+ name = name.to_s
23
+
24
+ definition = HasPreference::Definition.new(name, *args)
25
+ self.preference_definitions[name] = definition
26
+
27
+ name = name.gsub(/[^A-Za-z0-9_-]/, '').underscore
28
+
29
+ define_method("preferred_#{name}?") do |*group|
30
+ preferred?(name, group.first)
31
+ end
32
+
33
+ alias_method "prefers_#{name}?", "preferred_#{name}?"
34
+
35
+ define_method("preferred_#{name}") do |*group|
36
+ preferred(name, group.first)
37
+ end
38
+
39
+ alias_method "prefers_#{name}", "preferred_#{name}"
40
+
41
+ define_method("preferred_#{name}=") do |*args|
42
+ write_preference(*args.flatten.unshift(name))
43
+ end
44
+
45
+ alias_method "prefers_#{name}=", "preferred_#{name}="
46
+
47
+ define_method("preferred_#{name}_changed?") do |*group|
48
+ preference_changed?(name, group.first)
49
+ end
50
+
51
+ alias_method "prefers_#{name}_changed?", "preferred_#{name}_changed?"
52
+
53
+ define_method("preferred_#{name}_was") do |*group|
54
+ preference_was(name, group.first)
55
+ end
56
+
57
+ alias_method "prefers_#{name}_was", "preferred_#{name}_was"
58
+
59
+ define_method("preferred_#{name}_change") do |*group|
60
+ preference_change(name, group.first)
61
+ end
62
+
63
+ alias_method "prefers_#{name}_change", "preferred_#{name}_change"
64
+
65
+ define_method("preferred_#{name}_will_change!") do |*group|
66
+ preference_will_change!(name, group.first)
67
+ end
68
+
69
+ alias_method "prefers_#{name}_will_change!", "preferred_#{name}_will_change!"
70
+
71
+ define_method("reset_preferred_#{name}!") do |*group|
72
+ reset_preference!(name, group.first)
73
+ end
74
+
75
+ alias_method "reset_prefers_#{name}!", "reset_preferred_#{name}!"
76
+
77
+ definition
78
+ end
79
+ end
80
+
81
+ module ClassMethods
82
+ def build_preference_scope(preferences, inverse = false)
83
+ joins = []
84
+ statements = []
85
+ values = []
86
+
87
+ preferences = preferences.inject({}) do |result, (group, value)|
88
+ if value.is_a?(Hash)
89
+ value.each {|preference, value| result[[group, preference]] = value}
90
+ else
91
+ result[[nil, group]] = value
92
+ end
93
+
94
+ result
95
+ end
96
+
97
+ preferences.each do |(group, preference), value|
98
+ group_id, group_type = Preference.split_group(group)
99
+
100
+ preference = preference.to_s
101
+ definition = preference_definitions[preference.to_s]
102
+
103
+ value = definition.type_cast(value)
104
+ is_default = definition.default_value(group_type) == value
105
+
106
+ table = "preferences_#{group_id}_#{group_type}_#{preference}"
107
+
108
+ joins << "LEFT JOIN preferences AS #{table} ON #{table}.owner_id = #{table_name}.#{primary_key} AND " + sanitize_sql(
109
+ "#{table}.owner_type" => base_class.name.to_s,
110
+ "#{table}.group_id" => group_id,
111
+ "#{table}.group_type" => group_type,
112
+ "#{table}.name" => preference
113
+ )
114
+
115
+ if inverse
116
+ statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NOT NULL' : ' != ?') + (!is_default ? " OR #{table}.id IS NULL" : '')
117
+ else
118
+ statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NULL' : ' = ?') + (is_default ? " OR #{table}.id IS NULL" : '')
119
+ end
120
+
121
+ values << value unless value.nil?
122
+ end
123
+
124
+ sql = statements.map! {|statement| "(#{statement})"} * ' AND '
125
+ { :joins => joins, :conditions => values.unshift(sql) }
126
+ end
127
+ end
128
+
129
+ module InstanceMethods
130
+ def preferences(group = nil)
131
+ preferences = preferences_group(group)
132
+
133
+ unless preferences_group_loaded?(group)
134
+ group_id, group_type = Preference.split_group(group)
135
+
136
+ find_preferences(:group_id => group_id, :group_type => group_type).each do |preference|
137
+ preferences[preference.name] = preference.value unless preferences.include?(preference.name)
138
+ end
139
+
140
+ preference_definitions.each do |name, definition|
141
+ preferences[name] = definition.default_value(group_type) unless preferences.include?(name)
142
+ end
143
+ end
144
+
145
+ preferences.inject({}) do |typed_preferences, (name, value)|
146
+ typed_preferences[name] = value.nil? ? value : preference_definitions[name].type_cast(value)
147
+ typed_preferences
148
+ end
149
+ end
150
+
151
+ def preferred?(name, group = nil)
152
+ name = name.to_s
153
+ assert_valid_preference(name)
154
+
155
+ value = preferred(name, group)
156
+ preference_definitions[name].query(value)
157
+ end
158
+
159
+ alias_method :prefers?, :preferred?
160
+
161
+ def preferred(name, group = nil)
162
+ name = name.to_s
163
+ assert_valid_preference(name)
164
+
165
+ if preferences_group(group).include?(name)
166
+ value = preferences_group(group)[name]
167
+ else
168
+ group_id, group_type = Preference.split_group(group)
169
+ preference = find_preferences(:name => name, :group_id => group_id, :group_type => group_type).first unless preferences_group_loaded?(group)
170
+
171
+ value = preference ? preference.value : preference_definitions[name].default_value(group_type)
172
+ preferences_group(group)[name] = value
173
+ end
174
+
175
+ definition = preference_definitions[name]
176
+ value = definition.type_cast(value) unless value.nil?
177
+
178
+ value
179
+ end
180
+
181
+ alias_method :prefers, :preferred
182
+
183
+ def write_preference(name, value, group = nil)
184
+ name = name.to_s
185
+ assert_valid_preference(name)
186
+
187
+ preferences_changed = preferences_changed_group(group)
188
+
189
+ if preferences_changed.include?(name)
190
+ old = preferences_changed[name]
191
+ preferences_changed.delete(name) unless preference_value_changed?(name, old, value)
192
+ else
193
+ old = clone_preference_value(name, group)
194
+ preferences_changed[name] = old if preference_value_changed?(name, old, value)
195
+ end
196
+
197
+ value = convert_number_column_value(value) if preference_definitions[name].number?
198
+ preferences_group(group)[name] = value
199
+
200
+ value
201
+ end
202
+
203
+ def preferences_changed?(group = nil)
204
+ !preferences_changed_group(group).empty?
205
+ end
206
+
207
+ def preferences_changed(group = nil)
208
+ preferences_changed_group(group).keys
209
+ end
210
+
211
+ def preference_changes(group = nil)
212
+ preferences_changed(group).inject({}) do |changes, preference|
213
+ changes[preference] = preference_change(preference, group)
214
+ changes
215
+ end
216
+ end
217
+
218
+ def reload(*args) #:nodoc:
219
+ result = super
220
+
221
+ @preferences.clear if @preferences
222
+ @preferences_changed.clear if @preferences_changed
223
+
224
+ result
225
+ end
226
+
227
+ private
228
+ def assert_valid_preference(name)
229
+ raise(ArgumentError, "Unknown preference: #{name}") unless preference_definitions.include?(name)
230
+ end
231
+
232
+ def preferences_group(group)
233
+ @preferences ||= {}
234
+ @preferences[group.is_a?(Symbol) ? group.to_s : group] ||= {}
235
+ end
236
+
237
+ def preferences_group_loaded?(group)
238
+ preference_definitions.length == preferences_group(group).length
239
+ end
240
+
241
+ def clone_preference_value(name, group)
242
+ value = preferred(name, group)
243
+ value.duplicable? ? value.clone : value
244
+ rescue TypeError, NoMethodError
245
+ value
246
+ end
247
+
248
+ def preferences_changed_group(group)
249
+ @preferences_changed ||= {}
250
+ @preferences_changed[group.is_a?(Symbol) ? group.to_s : group] ||= {}
251
+ end
252
+
253
+ def preference_changed?(name, group)
254
+ preferences_changed_group(group).include?(name)
255
+ end
256
+
257
+ def preference_change(name, group)
258
+ [preferences_changed_group(group)[name], preferred(name, group)] if preference_changed?(name, group)
259
+ end
260
+
261
+ def preference_was(name, group)
262
+ preference_changed?(name, group) ? preferences_changed_group(group)[name] : preferred(name, group)
263
+ end
264
+
265
+ def preference_will_change!(name, group)
266
+ preferences_changed_group(group)[name] = clone_preference_value(name, group)
267
+ end
268
+
269
+ def reset_preference!(name, group)
270
+ write_preference(name, preferences_changed_group(group)[name], group) if preference_changed?(name, group)
271
+ end
272
+
273
+ def preference_value_changed?(name, old, value)
274
+ definition = preference_definitions[name]
275
+
276
+ if definition.type == :integer && (old.nil? || old == 0)
277
+ value = nil if value.blank?
278
+ else
279
+ value = definition.type_cast(value)
280
+ end
281
+
282
+ old != value
283
+ end
284
+
285
+ def update_preferences
286
+ if @preferences_changed
287
+ @preferences_changed.each do |group, preferences|
288
+ group_id, group_type = Preference.split_group(group)
289
+
290
+ preferences.keys.each do |name|
291
+ attributes = {:name => name, :group_id => group_id, :group_type => group_type}
292
+
293
+ preference = find_preferences(attributes).first || stored_preferences.build(attributes)
294
+ preference.value = preferred(name, group)
295
+
296
+ preference.save!
297
+ end
298
+ end
299
+
300
+ @preferences_changed.clear
301
+ end
302
+ end
303
+
304
+ def find_preferences(attributes)
305
+ if stored_preferences.loaded?
306
+ stored_preferences.select do |preference|
307
+ attributes.all? {|attribute, value| preference[attribute] == value}
308
+ end
309
+ else
310
+ stored_preferences.find(:all, :conditions => attributes)
311
+ end
312
+ end
313
+ end
314
+
315
+ class Definition
316
+ attr_reader :type
317
+
318
+ def initialize(name, *args)
319
+ options = args.extract_options!
320
+ options.assert_valid_keys(:default, :group_defaults)
321
+
322
+ @type = args.first ? args.first.to_sym : :boolean
323
+ @column = ActiveRecord::ConnectionAdapters::Column.new(name.to_s, options[:default], @type == :any ? nil : @type.to_s)
324
+
325
+ @group_defaults = (options[:group_defaults] || {}).inject({}) do |defaults, (group, default)|
326
+ defaults[group.is_a?(Symbol) ? group.to_s : group] = type_cast(default)
327
+ defaults
328
+ end
329
+ end
330
+
331
+ def name
332
+ @column.name
333
+ end
334
+
335
+ def default_value(group = nil)
336
+ @group_defaults.include?(group) ? @group_defaults[group] : @column.default
337
+ end
338
+
339
+ def number?
340
+ @column.number?
341
+ end
342
+
343
+ def type_cast(value)
344
+ @type == :any ? value : @column.type_cast(value)
345
+ end
346
+
347
+ def query(value)
348
+ if !(value = type_cast(value))
349
+ false
350
+ elsif number?
351
+ !value.zero?
352
+ else
353
+ !value.blank?
354
+ end
355
+ end
356
+ end
357
+ end
358
+
359
+ ActiveRecord::Base.class_eval do
360
+ extend HasPreference::MacroMethods
361
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_preference
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Thomas Boerger
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-01 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: Preference for ActiveRecord models
22
+ email: tboerger@tbpro.de
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README
29
+ - LICENSE
30
+ files:
31
+ - LICENSE
32
+ - README
33
+ - Rakefile
34
+ - init.rb
35
+ - app/models/preference.rb
36
+ - lib/generators/has_attribute/migration_generator.rb
37
+ - lib/generators/has_attribute/template/migration.rb
38
+ - lib/generators/has_preference/migration/migration_generator.rb
39
+ - lib/generators/has_preference/migration/templates/migration.rb
40
+ - lib/generators/has_preference.rb
41
+ - lib/has_preference.rb
42
+ homepage: http://github.com/tbpro/has_preference
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --line-numbers
48
+ - --main=README
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.7.2
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Has preference
76
+ test_files: []
77
+