granular_permissions 0.0.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.
Files changed (76) hide show
  1. data/.bundle/config +2 -0
  2. data/.config/cucumber.yml +8 -0
  3. data/.gitignore +3 -0
  4. data/.rvmrc +1 -0
  5. data/Gemfile +29 -0
  6. data/Gemfile.lock +161 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README +13 -0
  9. data/Rakefile +56 -0
  10. data/VERSION +1 -0
  11. data/features/step_definitions/bernie_steps.rb +5 -0
  12. data/features/step_definitions/mislav_steps.rb +13 -0
  13. data/features/step_definitions/web_steps.rb +226 -0
  14. data/features/support/blueprints.rb +13 -0
  15. data/features/support/env.rb +39 -0
  16. data/features/support/models.rb +7 -0
  17. data/features/support/paths.rb +41 -0
  18. data/fixture_rails_root/.gitignore +4 -0
  19. data/fixture_rails_root/Gemfile +25 -0
  20. data/fixture_rails_root/Gemfile.lock +177 -0
  21. data/fixture_rails_root/README +256 -0
  22. data/fixture_rails_root/Rakefile +7 -0
  23. data/fixture_rails_root/app/controllers/application_controller.rb +3 -0
  24. data/fixture_rails_root/app/helpers/application_helper.rb +2 -0
  25. data/fixture_rails_root/app/views/layouts/application.html.erb +14 -0
  26. data/fixture_rails_root/config.ru +4 -0
  27. data/fixture_rails_root/config/application.rb +16 -0
  28. data/fixture_rails_root/config/boot.rb +13 -0
  29. data/fixture_rails_root/config/database.yml +28 -0
  30. data/fixture_rails_root/config/environment.rb +5 -0
  31. data/fixture_rails_root/config/environments/development.rb +11 -0
  32. data/fixture_rails_root/config/environments/production.rb +9 -0
  33. data/fixture_rails_root/config/environments/test.rb +10 -0
  34. data/fixture_rails_root/config/initializers/backtrace_silencers.rb +7 -0
  35. data/fixture_rails_root/config/initializers/inflections.rb +10 -0
  36. data/fixture_rails_root/config/initializers/mime_types.rb +5 -0
  37. data/fixture_rails_root/config/initializers/reporter.rb +1 -0
  38. data/fixture_rails_root/config/initializers/secret_token.rb +7 -0
  39. data/fixture_rails_root/config/initializers/session_store.rb +2 -0
  40. data/fixture_rails_root/config/locales/en.yml +5 -0
  41. data/fixture_rails_root/config/routes.rb +3 -0
  42. data/fixture_rails_root/db/schema.rb +30 -0
  43. data/fixture_rails_root/db/seeds.rb +7 -0
  44. data/fixture_rails_root/doc/README_FOR_APP +2 -0
  45. data/fixture_rails_root/lib/tasks/.gitkeep +0 -0
  46. data/fixture_rails_root/public/404.html +26 -0
  47. data/fixture_rails_root/public/422.html +26 -0
  48. data/fixture_rails_root/public/500.html +26 -0
  49. data/fixture_rails_root/public/favicon.ico +0 -0
  50. data/fixture_rails_root/public/images/rails.png +0 -0
  51. data/fixture_rails_root/public/index.html +239 -0
  52. data/fixture_rails_root/public/javascripts/application.js +2 -0
  53. data/fixture_rails_root/public/javascripts/controls.js +965 -0
  54. data/fixture_rails_root/public/javascripts/dragdrop.js +974 -0
  55. data/fixture_rails_root/public/javascripts/effects.js +1123 -0
  56. data/fixture_rails_root/public/javascripts/prototype.js +6001 -0
  57. data/fixture_rails_root/public/javascripts/rails.js +175 -0
  58. data/fixture_rails_root/public/robots.txt +5 -0
  59. data/fixture_rails_root/public/stylesheets/.gitkeep +0 -0
  60. data/fixture_rails_root/script/rails +6 -0
  61. data/fixture_rails_root/test/performance/browsing_test.rb +9 -0
  62. data/fixture_rails_root/test/test_helper.rb +13 -0
  63. data/granular_permissions.gemspec +122 -0
  64. data/init.rb +1 -0
  65. data/install.rb +1 -0
  66. data/lib/flag_shih_tzu.rb +210 -0
  67. data/lib/generators/USAGE +8 -0
  68. data/lib/generators/granular_permissions_generator.rb +3 -0
  69. data/lib/granular_permissions.rb +3 -0
  70. data/spec/database.yml +4 -0
  71. data/spec/schema.rb +22 -0
  72. data/spec/spec_helper.rb +25 -0
  73. data/spec/support/blueprints.rb +13 -0
  74. data/spec/support/models.rb +3 -0
  75. data/uninstall.rb +1 -0
  76. metadata +161 -0
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'rails/performance_test_help'
3
+
4
+ # Profiling results for each test method are written to tmp/performance.
5
+ class BrowsingTest < ActionDispatch::PerformanceTest
6
+ def test_homepage
7
+ get '/'
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
7
+ #
8
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
9
+ # -- they do not yet inherit this setting
10
+ fixtures :all
11
+
12
+ # Add more helper methods to be used by all tests here...
13
+ end
@@ -0,0 +1,122 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{granular_permissions}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Bernardo Telles"]
12
+ s.date = %q{2010-11-05}
13
+ s.description = %q{engine that allows users to specify really tiny permissions levels}
14
+ s.email = %q{btelles@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".bundle/config",
20
+ ".config/cucumber.yml",
21
+ ".gitignore",
22
+ ".rvmrc",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "MIT-LICENSE",
26
+ "README",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "features/step_definitions/bernie_steps.rb",
30
+ "features/step_definitions/mislav_steps.rb",
31
+ "features/step_definitions/web_steps.rb",
32
+ "features/support/blueprints.rb",
33
+ "features/support/debug.log",
34
+ "features/support/env.rb",
35
+ "features/support/models.rb",
36
+ "features/support/paths.rb",
37
+ "fixture_rails_root/.gitignore",
38
+ "fixture_rails_root/Gemfile",
39
+ "fixture_rails_root/Gemfile.lock",
40
+ "fixture_rails_root/README",
41
+ "fixture_rails_root/Rakefile",
42
+ "fixture_rails_root/app/controllers/application_controller.rb",
43
+ "fixture_rails_root/app/helpers/application_helper.rb",
44
+ "fixture_rails_root/app/views/layouts/application.html.erb",
45
+ "fixture_rails_root/config.ru",
46
+ "fixture_rails_root/config/application.rb",
47
+ "fixture_rails_root/config/boot.rb",
48
+ "fixture_rails_root/config/database.yml",
49
+ "fixture_rails_root/config/environment.rb",
50
+ "fixture_rails_root/config/environments/development.rb",
51
+ "fixture_rails_root/config/environments/production.rb",
52
+ "fixture_rails_root/config/environments/test.rb",
53
+ "fixture_rails_root/config/initializers/backtrace_silencers.rb",
54
+ "fixture_rails_root/config/initializers/inflections.rb",
55
+ "fixture_rails_root/config/initializers/mime_types.rb",
56
+ "fixture_rails_root/config/initializers/reporter.rb",
57
+ "fixture_rails_root/config/initializers/secret_token.rb",
58
+ "fixture_rails_root/config/initializers/session_store.rb",
59
+ "fixture_rails_root/config/locales/en.yml",
60
+ "fixture_rails_root/config/routes.rb",
61
+ "fixture_rails_root/db/schema.rb",
62
+ "fixture_rails_root/db/seeds.rb",
63
+ "fixture_rails_root/doc/README_FOR_APP",
64
+ "fixture_rails_root/lib/tasks/.gitkeep",
65
+ "fixture_rails_root/public/404.html",
66
+ "fixture_rails_root/public/422.html",
67
+ "fixture_rails_root/public/500.html",
68
+ "fixture_rails_root/public/favicon.ico",
69
+ "fixture_rails_root/public/images/rails.png",
70
+ "fixture_rails_root/public/index.html",
71
+ "fixture_rails_root/public/javascripts/application.js",
72
+ "fixture_rails_root/public/javascripts/controls.js",
73
+ "fixture_rails_root/public/javascripts/dragdrop.js",
74
+ "fixture_rails_root/public/javascripts/effects.js",
75
+ "fixture_rails_root/public/javascripts/prototype.js",
76
+ "fixture_rails_root/public/javascripts/rails.js",
77
+ "fixture_rails_root/public/robots.txt",
78
+ "fixture_rails_root/public/stylesheets/.gitkeep",
79
+ "fixture_rails_root/script/rails",
80
+ "fixture_rails_root/test/performance/browsing_test.rb",
81
+ "fixture_rails_root/test/test_helper.rb",
82
+ "granular_permissions.gemspec",
83
+ "init.rb",
84
+ "install.rb",
85
+ "lib/flag_shih_tzu.rb",
86
+ "lib/generators/USAGE",
87
+ "lib/generators/granular_permissions_generator.rb",
88
+ "lib/granular_permissions.rb",
89
+ "spec/database.yml",
90
+ "spec/db/granular_permissions_plugin.sqlite3.db",
91
+ "spec/schema.rb",
92
+ "spec/spec_helper.rb",
93
+ "spec/support/blueprints.rb",
94
+ "spec/support/models.rb",
95
+ "uninstall.rb"
96
+ ]
97
+ s.homepage = %q{http://github.com/btelles/granular_permissions}
98
+ s.rdoc_options = ["--charset=UTF-8"]
99
+ s.require_paths = ["lib"]
100
+ s.rubygems_version = %q{1.3.7}
101
+ s.summary = %q{engine that allows users to specify really tiny permissions levels}
102
+ s.test_files = [
103
+ "spec/support/blueprints.rb",
104
+ "spec/support/models.rb",
105
+ "spec/spec_helper.rb",
106
+ "spec/schema.rb"
107
+ ]
108
+
109
+ if s.respond_to? :specification_version then
110
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
111
+ s.specification_version = 3
112
+
113
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
114
+ s.add_development_dependency(%q<rspec>, ["= 2.0.1"])
115
+ else
116
+ s.add_dependency(%q<rspec>, ["= 2.0.1"])
117
+ end
118
+ else
119
+ s.add_dependency(%q<rspec>, ["= 2.0.1"])
120
+ end
121
+ end
122
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ # Include hook code here
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,210 @@
1
+ module FlagShihTzu
2
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'] # taken from ActiveRecord::ConnectionAdapters::Column
3
+ DEFAULT_COLUMN_NAME = 'flags'
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ class IncorrectFlagColumnException < Exception; end
10
+ class NoSuchFlagQueryModeException < Exception; end
11
+ class NoSuchFlagException < Exception; end
12
+
13
+ module ClassMethods
14
+ def has_flags(*args)
15
+ flag_hash, opts = parse_options(*args)
16
+ opts = {
17
+ :named_scopes => true,
18
+ :column => DEFAULT_COLUMN_NAME,
19
+ :flag_query_mode => :in_list
20
+ }.update(opts)
21
+ colmn = opts[:column]
22
+
23
+ return unless check_flag_column(colmn)
24
+
25
+ # options are stored in a class level hash and apply per-column
26
+ class_inheritable_hash :flag_options
27
+ write_inheritable_attribute(:flag_options, {}) if flag_options.nil?
28
+ flag_options[colmn] = opts
29
+
30
+ # the mappings are stored in this class level hash and apply per-column
31
+ class_inheritable_hash :flag_mapping
32
+ write_inheritable_attribute(:flag_mapping, {}) if flag_mapping.nil?
33
+ flag_mapping[colmn] ||= {}
34
+
35
+ flag_hash.each do |flag_key, flag_name|
36
+ raise ArgumentError, "has_flags: flag keys should be positive integers, and #{flag_key} is not" unless is_valid_flag_key(flag_key)
37
+ raise ArgumentError, "has_flags: flag names should be symbols, and #{flag_name} is not" unless is_valid_flag_name(flag_name)
38
+ next if flag_mapping[colmn][flag_name] & (1 << (flag_key - 1)) # next if already methods defined by flagshitzu
39
+ raise ArgumentError, "has_flags: flag name #{flag_name} already defined, please choose different name" if method_defined?(flag_name)
40
+
41
+ flag_mapping[colmn][flag_name] = 1 << (flag_key - 1)
42
+
43
+ class_eval <<-EVAL
44
+ def #{flag_name}
45
+ flag_enabled?(:#{flag_name}, '#{colmn}')
46
+ end
47
+
48
+ def #{flag_name}?
49
+ flag_enabled?(:#{flag_name}, '#{colmn}')
50
+ end
51
+
52
+ def #{flag_name}=(value)
53
+ FlagShihTzu::TRUE_VALUES.include?(value) ? enable_flag(:#{flag_name}, '#{colmn}') : disable_flag(:#{flag_name}, '#{colmn}')
54
+ end
55
+
56
+ def self.#{flag_name}_condition(options = {})
57
+ sql_condition_for_flag(:#{flag_name}, '#{colmn}', true, options[:table_alias] || self.table_name)
58
+ end
59
+
60
+ def self.not_#{flag_name}_condition
61
+ sql_condition_for_flag(:#{flag_name}, '#{colmn}', false)
62
+ end
63
+ EVAL
64
+
65
+ # Define the named scopes if the user wants them and AR supports it
66
+ if flag_options[colmn][:named_scopes] && respond_to?(named_scope_method)
67
+ class_eval <<-EVAL
68
+ #{named_scope_method} :#{flag_name}, lambda { { :conditions => #{flag_name}_condition } }
69
+ #{named_scope_method} :not_#{flag_name}, lambda { { :conditions => not_#{flag_name}_condition } }
70
+ EVAL
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ def check_flag(flag, colmn)
77
+ raise ArgumentError, "Column name '#{colmn}' for flag '#{flag}' is not a string" unless colmn.is_a?(String)
78
+ raise ArgumentError, "Invalid flag '#{flag}'" if flag_mapping[colmn].nil? || !flag_mapping[colmn].include?(flag)
79
+ end
80
+
81
+ private
82
+
83
+ def parse_options(*args)
84
+ options = args.shift
85
+ if args.size >= 1
86
+ add_options = args.shift
87
+ else
88
+ add_options = options.keys.select {|key| !key.is_a?(Fixnum)}.inject({}) do |hash, key|
89
+ hash[key] = options.delete(key)
90
+ hash
91
+ end
92
+ end
93
+ return options, add_options
94
+ end
95
+
96
+ def check_flag_column(colmn, table_name = self.table_name)
97
+ # If you aren't using ActiveRecord (eg. you are outside rails) then do not fail here
98
+ # If you are using ActiveRecord then you only want to check for the table if the table exists so it won't fail pre-migration
99
+ has_ar = !!defined?(ActiveRecord) && self.respond_to?(:descends_from_active_record?)
100
+ # Supposedly Rails 2.3 takes care of this, but this precaution is needed for backwards compatibility
101
+ has_table = has_ar ? ActiveRecord::Base.connection.tables.include?(table_name) : true
102
+
103
+ logger.warn("Error: Table '#{table_name}' doesn't exist") and return false unless has_table
104
+
105
+ if !has_ar || (has_ar && has_table)
106
+ if found_column = columns.find {|column| column.name == colmn}
107
+ raise IncorrectFlagColumnException, "Warning: Column '#{colmn}'must be of type integer in order to use FlagShihTzu" unless found_column.type == :integer
108
+ else
109
+ # Do not raise an exception since the migration to add the flags column might still be pending
110
+ logger.warn("Warning: Table '#{table_name}' must have an integer column named '#{colmn}' in order to use FlagShihTzu") and return false
111
+ end
112
+ end
113
+
114
+ true
115
+ end
116
+
117
+ def sql_condition_for_flag(flag, colmn, enabled = true, table_name = self.table_name)
118
+ check_flag(flag, colmn)
119
+
120
+ if flag_options[colmn][:flag_query_mode] == :bit_operator
121
+ # use & bit operator directly in the SQL query.
122
+ # This has the drawback of not using an index on the flags colum.
123
+ "(#{table_name}.#{colmn} & #{flag_mapping[colmn][flag]} = #{enabled ? flag_mapping[colmn][flag] : 0})"
124
+ elsif flag_options[colmn][:flag_query_mode] == :in_list
125
+ # use IN() operator in the SQL query.
126
+ # This has the drawback of becoming a big query when you have lots of flags.
127
+ neg = enabled ? "" : "not "
128
+ "(#{table_name}.#{colmn} #{neg}in (#{sql_in_for_flag(flag, colmn).join(',')}))"
129
+ else
130
+ raise NoSuchFlagQueryModeException
131
+ end
132
+ end
133
+
134
+ # returns an array of integers suitable for a SQL IN statement.
135
+ def sql_in_for_flag(flag, colmn)
136
+ val = flag_mapping[colmn][flag]
137
+ num = 2 ** flag_mapping[flag_options[colmn][:column]].length
138
+ (1..num).select {|i| i & val == val}
139
+ end
140
+
141
+ def is_valid_flag_key(flag_key)
142
+ flag_key > 0 && flag_key == flag_key.to_i
143
+ end
144
+
145
+ def is_valid_flag_name(flag_name)
146
+ flag_name.is_a?(Symbol)
147
+ end
148
+
149
+ # Returns the correct method to create a named scope.
150
+ # Use to prevent deprecation notices on Rails 3 when using +named_scope+ instead of +scope+.
151
+ def named_scope_method
152
+ # Can't use respond_to because both AR 2 and 3 respond to both +scope+ and +named_scope+.
153
+ ActiveRecord::VERSION::MAJOR == 2 ? :named_scope : :scope
154
+ end
155
+ end
156
+
157
+ # Performs the bitwise operation so the flag will return +true+.
158
+ def enable_flag(flag, colmn = nil)
159
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
160
+ self.class.check_flag(flag, colmn)
161
+
162
+ set_flags(self.flags(colmn) | self.class.flag_mapping[colmn][flag], colmn)
163
+ end
164
+
165
+ # Performs the bitwise operation so the flag will return +false+.
166
+ def disable_flag(flag, colmn = nil)
167
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
168
+ self.class.check_flag(flag, colmn)
169
+
170
+ set_flags(self.flags(colmn) & ~self.class.flag_mapping[colmn][flag], colmn)
171
+ end
172
+
173
+ def flag_enabled?(flag, colmn = nil)
174
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
175
+ self.class.check_flag(flag, colmn)
176
+
177
+ get_bit_for(flag, colmn) == 0 ? false : true
178
+ end
179
+
180
+ def flag_disabled?(flag, colmn = nil)
181
+ colmn = determine_flag_colmn_for(flag) if colmn.nil?
182
+ self.class.check_flag(flag, colmn)
183
+
184
+ !flag_enabled?(flag, colmn)
185
+ end
186
+
187
+ def flags(colmn = DEFAULT_COLUMN_NAME)
188
+ self[colmn] || 0
189
+ end
190
+
191
+ def set_flags(value, colmn)
192
+ self[colmn] = value
193
+ end
194
+
195
+ private
196
+
197
+ def get_bit_for(flag, colmn)
198
+ self.flags(colmn) & self.class.flag_mapping[colmn][flag]
199
+ end
200
+
201
+ def determine_flag_colmn_for(flag)
202
+ return DEFAULT_COLUMN_NAME if self.class.flag_mapping.nil?
203
+ self.class.flag_mapping.each_pair do |colmn, mapping|
204
+ return colmn if mapping.include?(flag)
205
+ end
206
+ raise NoSuchFlagException.new("determine_flag_colmn_for: Couldn't determine column for your flags!")
207
+ end
208
+
209
+ end
210
+