zendesk-features 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/README.rdoc +105 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/lib/features.rb +3 -0
- data/lib/features/active_record_extension.rb +135 -0
- data/lib/features/feature.rb +134 -0
- data/lib/features/features_helper.rb +18 -0
- data/test/database.yml +7 -0
- data/test/features_test.rb +96 -0
- data/test/fixtures/accounts.yml +3 -0
- data/test/fixtures/features.yml +7 -0
- data/test/schema.rb +27 -0
- data/test/test_helper.rb +37 -0
- metadata +69 -0
data/.gitignore
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
= Features
|
2
|
+
Features is a nice little gem for associating application features with an active record model in a Rails app.
|
3
|
+
|
4
|
+
Developed and used on http://zendesk.com for account owners to switch different application features on and off.
|
5
|
+
|
6
|
+
== Sponsored by Zendesk - Enlightened Customer Support
|
7
|
+
|
8
|
+
== Description
|
9
|
+
|
10
|
+
If the feature set of your application grows, some customers would like to limit the features to only include what they
|
11
|
+
need. To enable your customers at Zendesk to customize their help desk to their needs, we developed this nice lille feature
|
12
|
+
management system, that we thought someone else out there might find useful.
|
13
|
+
|
14
|
+
You can define your feature set in a nice directly in the class that should be associated with features:
|
15
|
+
|
16
|
+
class Account < ActiveRecord::Base
|
17
|
+
has_features do
|
18
|
+
feature :archive
|
19
|
+
feature :ssl
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Which would enable code like:
|
24
|
+
|
25
|
+
account.features.archive? # checks weather an account has the archive feature.
|
26
|
+
account.features.archive.create # adds the archive feature to the account
|
27
|
+
account.features.archive.destroy # removes the archive feature to the account
|
28
|
+
account.features?(:account, :ssl) # checks if the account has both the archive and ssl features
|
29
|
+
|
30
|
+
You can also define feature dependencies:
|
31
|
+
|
32
|
+
class Account < ActiveRecord::Base
|
33
|
+
has_features do
|
34
|
+
feature :archive
|
35
|
+
feature :premium
|
36
|
+
feature :ssl, :requires => [:premium]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Which would enable code like:
|
41
|
+
|
42
|
+
account.features.ssl.available? # returns true if all the required features of the ssl feature are met.
|
43
|
+
account.features.ssl.create # raises a Features::RequirementsError if the account doesn't have the premium feature
|
44
|
+
account.features.premium.destroy # would also destroy the ssl feature
|
45
|
+
|
46
|
+
Features can also be updated with the update_attributes and update_attributes! methods.
|
47
|
+
|
48
|
+
#assuming params include { :account => { :features => { :archive => '1', :ssl => '0' } } }
|
49
|
+
account.update_attributes(params[:account]) # would add the archive feature and remove the ssl feature
|
50
|
+
|
51
|
+
WARNING: You might want to protect some features from being updated with update_attributes. You can do this with:
|
52
|
+
|
53
|
+
class Account < ActiveRecord::Base
|
54
|
+
has_features do
|
55
|
+
feature :archive
|
56
|
+
feature :premium, :protected => true
|
57
|
+
feature :ssl, :requires => [:premium]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
This would protect the premium feature from being updated with update_attributes and update_attributes! The premium feature can only be updated with account.features.premium.create and account.features.premium.destroy.
|
62
|
+
|
63
|
+
We also created a view helper to help you generate the UI for enabling and disabling features:
|
64
|
+
|
65
|
+
<% form_for(:account, :html => { :method => :put }) do |f| %>
|
66
|
+
<h3>
|
67
|
+
<%= f.feature_check_box :ssl %> Enable SSL on your account
|
68
|
+
</h3>
|
69
|
+
<% end %>
|
70
|
+
|
71
|
+
== Known issues
|
72
|
+
|
73
|
+
Let us know if you find any.
|
74
|
+
|
75
|
+
== Requirements
|
76
|
+
|
77
|
+
* ActiveRecord
|
78
|
+
* ActionPack
|
79
|
+
|
80
|
+
== LICENSE:
|
81
|
+
|
82
|
+
(The MIT License)
|
83
|
+
|
84
|
+
Copyright (c) 2009 Zendesk
|
85
|
+
|
86
|
+
Permission is hereby granted, free of charge, to any person
|
87
|
+
obtaining a copy of this software and associated documentation
|
88
|
+
files (the "Software"), to deal in the Software without
|
89
|
+
restriction, including without limitation the rights to use,
|
90
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
91
|
+
copies of the Software, and to permit persons to whom the
|
92
|
+
Software is furnished to do so, subject to the following
|
93
|
+
conditions:
|
94
|
+
|
95
|
+
The above copyright notice and this permission notice shall be
|
96
|
+
included in all copies or substantial portions of the Software.
|
97
|
+
|
98
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
99
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
100
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
101
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
102
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
103
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
104
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
105
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "features"
|
8
|
+
gem.summary = %Q{Features is a nice little gem for associating application features with an active record model in a Rails app}
|
9
|
+
gem.email = "mick@zendesk.com"
|
10
|
+
gem.homepage = "http://github.com/zendesk/features"
|
11
|
+
gem.authors = ["Mick Staugaard", "Morten Primdahl"]
|
12
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
Rake::TestTask.new(:test) do |test|
|
21
|
+
test.libs << 'lib' << 'test'
|
22
|
+
test.pattern = 'test/**/*_test.rb'
|
23
|
+
test.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rcov/rcovtask'
|
28
|
+
Rcov::RcovTask.new do |test|
|
29
|
+
test.libs << 'test'
|
30
|
+
test.pattern = 'test/**/*_test.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
task :rcov do
|
35
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
if File.exist?('VERSION.yml')
|
45
|
+
config = YAML.load(File.read('VERSION.yml'))
|
46
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
47
|
+
else
|
48
|
+
version = ""
|
49
|
+
end
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "features #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
56
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/features.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
module Features
|
2
|
+
module ActiveRecordExtension
|
3
|
+
module ClassMethods
|
4
|
+
def has_features(&block)
|
5
|
+
builder = FeatureTreeBuilder.new(self)
|
6
|
+
builder.instance_eval(&block)
|
7
|
+
builder.build
|
8
|
+
|
9
|
+
has_many :features, :class_name => 'Features::Feature', :dependent => :destroy do
|
10
|
+
def available?(feature_name)
|
11
|
+
@owner.features?(*Feature.sym_to_class(feature_name).required_features.map(&:to_sym))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Define the feature query methods, given that the feature +wiffle+ is defined,
|
15
|
+
# then the methods +account.features.wiffle?+ and +account.features.wiffle!+ will
|
16
|
+
# be available
|
17
|
+
Feature::LIST.each do |f|
|
18
|
+
|
19
|
+
# The query method, does this account have a given feature? account.features.wiffle?
|
20
|
+
define_method "#{f}?" do
|
21
|
+
any? { |feature| feature.matches?(f) }
|
22
|
+
end
|
23
|
+
|
24
|
+
# The finder method which returns the feature if present, otherwise a new instance, allows
|
25
|
+
# non-destructive create and delete operations:
|
26
|
+
#
|
27
|
+
# account.features.wiffle.destroy
|
28
|
+
# account.features.wiffle.create
|
29
|
+
#
|
30
|
+
# In the latter case, a the +wiffle+ feature will only be enabled if it's not already. Be careful
|
31
|
+
# not to confuse this method with the "feature enabled" method above, ie. avoid
|
32
|
+
#
|
33
|
+
# format_c if account.feature.wiffle
|
34
|
+
#
|
35
|
+
define_method "#{f}" do
|
36
|
+
instance = detect { |feature| feature.matches?(f) }
|
37
|
+
instance ||= Feature.sym_to_class(f).new(@owner.class.name.underscore.to_sym => @owner)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
include Features::ActiveRecordExtension::InstanceMethods
|
43
|
+
alias_method_chain :update_attributes, :features
|
44
|
+
alias_method_chain :update_attributes!, :features
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
# Allows you to check for multiple features like account.features?(:suggestions, :suggestions_on_web)
|
51
|
+
def features?(*feature_names)
|
52
|
+
feature_names.all? { |feature_name| features.send("#{feature_name}?") }
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_attributes_with_features(attributes)
|
56
|
+
update_feature_attributes(attributes)
|
57
|
+
update_attributes_without_features(attributes)
|
58
|
+
end
|
59
|
+
|
60
|
+
def update_attributes_with_features!(attributes)
|
61
|
+
update_feature_attributes(attributes)
|
62
|
+
update_attributes_without_features!(attributes)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def update_feature_attributes(attributes)
|
68
|
+
if attributes && feature_attributes = attributes.delete(:features)
|
69
|
+
feature_attributes.each do |feature_name, value|
|
70
|
+
feature = features.send(feature_name)
|
71
|
+
if feature.protected?
|
72
|
+
logger.warn("someone tried to mass update the protected #{feature_name} feature")
|
73
|
+
else
|
74
|
+
if value == '1' || value == true
|
75
|
+
feature.create
|
76
|
+
else
|
77
|
+
feature.destroy
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
features.reset
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class FeatureTreeBuilder
|
87
|
+
def initialize(owner_class)
|
88
|
+
@owner_class = owner_class
|
89
|
+
@definitions = Hash.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def feature(name, options = {})
|
93
|
+
raise("Feature name '#{name}' is too long. Max 28 characters please...") if name.to_s.size > 28
|
94
|
+
feature_options = options.reverse_merge({:requires => [], :dependants => [], :protected => false})
|
95
|
+
feature_options[:requires] = [*feature_options[:requires]].uniq
|
96
|
+
@definitions[name.to_sym] = feature_options
|
97
|
+
end
|
98
|
+
|
99
|
+
def resolve_dependencies
|
100
|
+
@definitions.each do |name, options|
|
101
|
+
options[:requires].each do |required_feature_name|
|
102
|
+
@definitions[required_feature_name][:dependants] << name
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def build
|
108
|
+
resolve_dependencies
|
109
|
+
#defines the feature classes
|
110
|
+
@definitions.each do |name, options|
|
111
|
+
new_feature = Object.const_set(Feature.sym_to_name(name), Class.new(Feature))
|
112
|
+
new_feature.feature_owner = @owner_class
|
113
|
+
end
|
114
|
+
|
115
|
+
#sets the feature classes options
|
116
|
+
@definitions.each do |name, options|
|
117
|
+
new_feature = Feature.sym_to_class(name)
|
118
|
+
new_feature.protect! if options[:protected]
|
119
|
+
new_feature.required_features = options[:requires].map {|f| Feature.sym_to_class(f)}
|
120
|
+
new_feature.dependant_features = options[:dependants].map {|f| Feature.sym_to_class(f)}
|
121
|
+
|
122
|
+
Feature::LIST << name
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.included(receiver)
|
128
|
+
receiver.extend ClassMethods
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
ActiveRecord::Base.class_eval do
|
134
|
+
include Features::ActiveRecordExtension
|
135
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'features/active_record_extension'
|
2
|
+
|
3
|
+
module Features
|
4
|
+
class RequirementsError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# If you want to add a feature, simply define a suitable symbol to Feature::LIST. If for example you
|
8
|
+
# want to define the "wiffle feature", add :wiffle and you can subsequenctly do:
|
9
|
+
#
|
10
|
+
# account.features.wiffle?
|
11
|
+
# account.features.wiffle.destroy
|
12
|
+
# account.features.wiffle.create
|
13
|
+
#
|
14
|
+
class Feature < ActiveRecord::Base
|
15
|
+
abstract_class = true
|
16
|
+
|
17
|
+
LIST = []
|
18
|
+
|
19
|
+
validate_on_create :unique_type
|
20
|
+
after_create :reset_owner_association
|
21
|
+
after_destroy :reset_owner_association
|
22
|
+
before_destroy :destroy_dependant_features
|
23
|
+
|
24
|
+
def unique_type
|
25
|
+
errors.add(:feature, :taken) if self.class.exists?(self.class.feature_owner_key => send(self.class.feature_owner_key))
|
26
|
+
end
|
27
|
+
|
28
|
+
def available?
|
29
|
+
feature_owner_instance.features.available?(to_sym)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create
|
33
|
+
if new_record?
|
34
|
+
raise RequirementsError.new unless available?
|
35
|
+
super
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def matches?(sym)
|
41
|
+
to_sym == sym.to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_sym
|
45
|
+
@sym ||= Feature.class_to_sym(self.class)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.to_sym
|
49
|
+
@sym ||= Feature.class_to_sym(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.class_to_sym(klass)
|
53
|
+
klass.name.tableize.tableize[0..-10].to_sym
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.sym_to_name(sym)
|
57
|
+
"#{sym.to_s.camelize}Feature"
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.sym_to_class(sym)
|
61
|
+
sym_to_name(sym).constantize
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.protected?
|
65
|
+
@protect || false
|
66
|
+
end
|
67
|
+
def protected?
|
68
|
+
self.class.protected?
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.required_features
|
72
|
+
@required_features ||= []
|
73
|
+
end
|
74
|
+
def required_features
|
75
|
+
self.class.required_features
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.dependant_features
|
79
|
+
@dependant_features ||= []
|
80
|
+
end
|
81
|
+
def dependant_features
|
82
|
+
self.class.dependant_features
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def feature_owner_instance
|
88
|
+
send(self.class.feature_owner)
|
89
|
+
end
|
90
|
+
def update_owner_timestamp
|
91
|
+
feature_owner_instance.update_attribute(:updated_at, Time.now)
|
92
|
+
end
|
93
|
+
|
94
|
+
def reset_owner_association
|
95
|
+
feature_owner_instance.features.reload
|
96
|
+
end
|
97
|
+
|
98
|
+
def destroy_dependant_features
|
99
|
+
dependant_features.each do |dependant|
|
100
|
+
feature_owner_instance.features.send(dependant.to_sym).destroy
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.protect!
|
105
|
+
@protect = true
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.required_features=(required_features)
|
109
|
+
@required_features = required_features
|
110
|
+
end
|
111
|
+
def self.dependant_features=(dependant_features)
|
112
|
+
@dependant_features = dependant_features
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.feature_owner=(owner_class)
|
116
|
+
@feature_owner_sym = owner_class.name.underscore.to_sym
|
117
|
+
belongs_to @feature_owner_sym
|
118
|
+
validates_presence_of @feature_owner_sym
|
119
|
+
if owner_class.column_names.include?('updated_at')
|
120
|
+
before_create :update_owner_timestamp
|
121
|
+
before_destroy :update_owner_timestamp
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.feature_owner
|
126
|
+
@feature_owner_sym
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.feature_owner_key
|
130
|
+
"#{@feature_owner}_id".to_sym
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActionView
|
2
|
+
module Helpers
|
3
|
+
def feature_check_box(model_name, method, options = {}, checked_value = "1", unchecked_value = "0")
|
4
|
+
account = @template.instance_variable_get("@#{model_name}")
|
5
|
+
throw "feature_check_box only work on models with features" unless account.respond_to?(:features)
|
6
|
+
options[:checked] = account.features.send("#{method}?")
|
7
|
+
options[:id] ||= "#{model_name}_features_#{method}"
|
8
|
+
options[:name] = "#{model_name}[features][#{method}]"
|
9
|
+
@template.check_box(model_name, "features_#{method}", options, checked_value, unchecked_value)
|
10
|
+
end
|
11
|
+
|
12
|
+
class FormBuilder
|
13
|
+
def feature_check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
14
|
+
@template.feature_check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Account < ActiveRecord::Base
|
4
|
+
has_features do
|
5
|
+
feature :archive_reports, :requires => [:archive, :reports]
|
6
|
+
feature :ssl, :protected => true
|
7
|
+
feature :archive
|
8
|
+
feature :reports
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class FeaturesTest < ActiveSupport::TestCase
|
13
|
+
fixtures :accounts, :features
|
14
|
+
|
15
|
+
test "that features can be created" do
|
16
|
+
a = Account.create(:name => 'name')
|
17
|
+
assert(a.features.empty?)
|
18
|
+
assert(!a.features.archive?)
|
19
|
+
assert(a.features.archive.create)
|
20
|
+
assert(a.features.size == 1)
|
21
|
+
assert(a.features.archive?)
|
22
|
+
assert(a.features.archive.id == a.features.archive.create.id)
|
23
|
+
end
|
24
|
+
|
25
|
+
test "that features can be destroyed" do
|
26
|
+
a = accounts(:account1)
|
27
|
+
assert(a.features.archive?)
|
28
|
+
assert(a.features.archive.destroy)
|
29
|
+
a.features.reload
|
30
|
+
assert(!a.features.archive?)
|
31
|
+
end
|
32
|
+
|
33
|
+
test "checks for features" do
|
34
|
+
a = accounts(:account1)
|
35
|
+
assert(a.features.archive?)
|
36
|
+
assert(a.features.ssl?)
|
37
|
+
assert(!a.features.reports?)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "validates feature requirements" do
|
41
|
+
a = accounts(:account1)
|
42
|
+
assert(a.features.reports.available?)
|
43
|
+
assert(!a.features.archive_reports.available?)
|
44
|
+
|
45
|
+
assert_raises Features::RequirementsError do
|
46
|
+
a.features.archive_reports.create
|
47
|
+
end
|
48
|
+
|
49
|
+
assert(a.features.reports.create)
|
50
|
+
assert(a.features.archive_reports.create)
|
51
|
+
end
|
52
|
+
|
53
|
+
test "destroys dependant features when destroyed" do
|
54
|
+
a = accounts(:account1)
|
55
|
+
assert(a.features.reports.create)
|
56
|
+
assert(a.features.archive_reports.create)
|
57
|
+
|
58
|
+
assert(a.features.archive.destroy)
|
59
|
+
a.features.reload
|
60
|
+
assert(!a.features.archive?)
|
61
|
+
assert(!a.features.archive_reports?)
|
62
|
+
assert(a.features.reports?)
|
63
|
+
end
|
64
|
+
|
65
|
+
test "mass updating should update features" do
|
66
|
+
a = Account.create(:name => 'name')
|
67
|
+
assert(a.features.empty?)
|
68
|
+
assert(!a.features.archive?)
|
69
|
+
assert(!a.features.reports?)
|
70
|
+
|
71
|
+
a.update_attributes(:features => {:archive => '1', :reports => '1'})
|
72
|
+
assert(a.features.archive?)
|
73
|
+
assert(a.features.reports?)
|
74
|
+
|
75
|
+
a.update_attributes(:features => {:archive => '0', :reports => '0'})
|
76
|
+
assert(!a.features.archive?)
|
77
|
+
assert(!a.features.reports?)
|
78
|
+
end
|
79
|
+
|
80
|
+
test "mass updating should not update protected features" do
|
81
|
+
a = Account.create(:name => 'name')
|
82
|
+
assert(a.features.empty?)
|
83
|
+
assert(!a.features.archive?)
|
84
|
+
assert(!a.features.ssl?)
|
85
|
+
|
86
|
+
a.update_attributes(:features => {:archive => '1', :ssl => '1'})
|
87
|
+
assert(a.features.archive?)
|
88
|
+
assert(!a.features.ssl?)
|
89
|
+
|
90
|
+
assert(a.features.ssl.create)
|
91
|
+
assert(a.features.ssl?)
|
92
|
+
a.update_attributes(:features => {:archive => '0', :ssl => '0'})
|
93
|
+
assert(!a.features.archive?)
|
94
|
+
assert(a.features.ssl?)
|
95
|
+
end
|
96
|
+
end
|
data/test/schema.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead of editing this file,
|
2
|
+
# please use the migrations feature of Active Record to incrementally modify your database, and
|
3
|
+
# then regenerate this schema definition.
|
4
|
+
#
|
5
|
+
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
|
6
|
+
# to create the application database on another system, you should be using db:schema:load, not running
|
7
|
+
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
8
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
9
|
+
#
|
10
|
+
# It's strongly recommended to check this file into your version control system.
|
11
|
+
|
12
|
+
ActiveRecord::Schema.define(:version => 1) do
|
13
|
+
|
14
|
+
create_table "features", :force => true do |t|
|
15
|
+
t.string "type"
|
16
|
+
t.integer "account_id"
|
17
|
+
t.datetime "created_at"
|
18
|
+
t.datetime "updated_at"
|
19
|
+
end
|
20
|
+
|
21
|
+
create_table "accounts", :force => true do |t|
|
22
|
+
t.string "name"
|
23
|
+
t.datetime "created_at"
|
24
|
+
t.datetime "updated_at"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
|
+
require 'active_record/fixtures'
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
8
|
+
ActiveRecord::Base.establish_connection('test')
|
9
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
10
|
+
|
11
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
14
|
+
require 'features'
|
15
|
+
|
16
|
+
class ActiveSupport::TestCase
|
17
|
+
include ActiveRecord::TestFixtures
|
18
|
+
|
19
|
+
def create_fixtures(*table_names)
|
20
|
+
if block_given?
|
21
|
+
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
|
22
|
+
else
|
23
|
+
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
|
28
|
+
self.use_transactional_fixtures = true
|
29
|
+
|
30
|
+
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
|
31
|
+
self.use_instantiated_fixtures = false
|
32
|
+
|
33
|
+
# Add more helper methods to be used by all tests here...
|
34
|
+
end
|
35
|
+
|
36
|
+
ActiveSupport::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
37
|
+
$LOAD_PATH.unshift(ActiveSupport::TestCase.fixture_path)
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zendesk-features
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mick Staugaard
|
8
|
+
- Morten Primdahl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-06-01 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description:
|
18
|
+
email: mick@zendesk.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .gitignore
|
27
|
+
- README.rdoc
|
28
|
+
- Rakefile
|
29
|
+
- VERSION
|
30
|
+
- lib/features.rb
|
31
|
+
- lib/features/active_record_extension.rb
|
32
|
+
- lib/features/feature.rb
|
33
|
+
- lib/features/features_helper.rb
|
34
|
+
- test/database.yml
|
35
|
+
- test/features_test.rb
|
36
|
+
- test/fixtures/accounts.yml
|
37
|
+
- test/fixtures/features.yml
|
38
|
+
- test/schema.rb
|
39
|
+
- test/test_helper.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/zendesk/features
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --charset=UTF-8
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.2.0
|
63
|
+
signing_key:
|
64
|
+
specification_version: 2
|
65
|
+
summary: Features is a nice little gem for associating application features with an active record model in a Rails app
|
66
|
+
test_files:
|
67
|
+
- test/features_test.rb
|
68
|
+
- test/schema.rb
|
69
|
+
- test/test_helper.rb
|