sparkly-auth 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/app/controllers/sparkly_accounts_controller.rb +59 -0
- data/app/controllers/sparkly_controller.rb +47 -0
- data/app/controllers/sparkly_sessions_controller.rb +52 -0
- data/app/models/password.rb +3 -0
- data/app/models/remembrance_token.rb +50 -0
- data/app/views/sparkly_accounts/edit.html.erb +24 -0
- data/app/views/sparkly_accounts/new.html.erb +24 -0
- data/app/views/sparkly_accounts/show.html.erb +0 -0
- data/app/views/sparkly_sessions/new.html.erb +22 -0
- data/dependencies.rb +1 -0
- data/generators/sparkly/USAGE +27 -0
- data/generators/sparkly/sparkly_generator.rb +76 -0
- data/generators/sparkly/templates/accounts_controller.rb +65 -0
- data/generators/sparkly/templates/accounts_helper.rb +2 -0
- data/generators/sparkly/templates/help_file.txt +56 -0
- data/generators/sparkly/templates/initializer.rb +30 -0
- data/generators/sparkly/templates/migrations/add_confirmed_to_sparkly_passwords.rb +9 -0
- data/generators/sparkly/templates/migrations/create_sparkly_passwords.rb +19 -0
- data/generators/sparkly/templates/migrations/create_sparkly_remembered_tokens.rb +15 -0
- data/generators/sparkly/templates/sessions_controller.rb +45 -0
- data/generators/sparkly/templates/sessions_helper.rb +2 -0
- data/generators/sparkly/templates/tasks/migrations.rb +1 -0
- data/generators/sparkly/templates/views/sparkly_accounts/edit.html.erb +24 -0
- data/generators/sparkly/templates/views/sparkly_accounts/new.html.erb +24 -0
- data/generators/sparkly/templates/views/sparkly_accounts/show.html.erb +0 -0
- data/generators/sparkly/templates/views/sparkly_sessions/new.html.erb +22 -0
- data/init.rb +44 -0
- data/lib/auth.rb +52 -0
- data/lib/auth/behavior/base.rb +64 -0
- data/lib/auth/behavior/core.rb +87 -0
- data/lib/auth/behavior/core/authenticated_model_methods.rb +52 -0
- data/lib/auth/behavior/core/controller_extensions.rb +52 -0
- data/lib/auth/behavior/core/controller_extensions/class_methods.rb +24 -0
- data/lib/auth/behavior/core/controller_extensions/current_user.rb +54 -0
- data/lib/auth/behavior/core/password_methods.rb +65 -0
- data/lib/auth/behavior/remember_me.rb +17 -0
- data/lib/auth/behavior/remember_me/configuration.rb +21 -0
- data/lib/auth/behavior/remember_me/controller_extensions.rb +66 -0
- data/lib/auth/behavior_lookup.rb +10 -0
- data/lib/auth/configuration.rb +328 -0
- data/lib/auth/encryptors/sha512.rb +20 -0
- data/lib/auth/generators/configuration_generator.rb +20 -0
- data/lib/auth/generators/controllers_generator.rb +34 -0
- data/lib/auth/generators/migration_generator.rb +32 -0
- data/lib/auth/generators/route_generator.rb +19 -0
- data/lib/auth/generators/views_generator.rb +26 -0
- data/lib/auth/model.rb +94 -0
- data/lib/auth/observer.rb +21 -0
- data/lib/auth/target_list.rb +5 -0
- data/lib/auth/tasks/migrations.rb +71 -0
- data/lib/auth/token.rb +10 -0
- data/lib/sparkly-auth.rb +1 -0
- data/rails/init.rb +17 -0
- data/rails/routes.rb +19 -0
- data/sparkly-auth.gemspec +143 -0
- data/spec/controllers/application_controller_spec.rb +13 -0
- data/spec/generators/sparkly_spec.rb +64 -0
- data/spec/lib/auth/behavior/core_spec.rb +184 -0
- data/spec/lib/auth/behavior/remember_me_spec.rb +127 -0
- data/spec/lib/auth/extensions/controller_spec.rb +32 -0
- data/spec/lib/auth/model_spec.rb +57 -0
- data/spec/lib/auth_spec.rb +32 -0
- data/spec/mocks/models/user.rb +3 -0
- data/spec/routes_spec.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/views_spec.rb +18 -0
- metadata +210 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
class <%=model.accounts_controller.camelize%>Controller < SparklyController
|
2
|
+
require_login_for :show, :edit, :update, :destroy
|
3
|
+
|
4
|
+
# GET new_model_url
|
5
|
+
def new
|
6
|
+
end
|
7
|
+
|
8
|
+
# POST model_url
|
9
|
+
def create
|
10
|
+
if model.save
|
11
|
+
login!(model)
|
12
|
+
redirect_back_or_default Auth.default_destination, Auth.account_created_message
|
13
|
+
else
|
14
|
+
render :action => 'new'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# GET model_url
|
19
|
+
def show
|
20
|
+
end
|
21
|
+
|
22
|
+
# GET edit_model_url
|
23
|
+
def edit
|
24
|
+
end
|
25
|
+
|
26
|
+
# PUT model_url
|
27
|
+
def update
|
28
|
+
if !model_params[:password].blank? || !model_params[:password_confirmation].blank?
|
29
|
+
model.password = model_params[:password]
|
30
|
+
model.password_confirmation = model_params[:password_confirmation]
|
31
|
+
end
|
32
|
+
|
33
|
+
if model.save
|
34
|
+
redirect_back_or_default user_path, Auth.account_updated_message
|
35
|
+
else
|
36
|
+
render :action => 'edit'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# DELETE model_url
|
41
|
+
def destroy
|
42
|
+
current_user && current_user.destroy
|
43
|
+
logout!
|
44
|
+
@current_user = nil
|
45
|
+
flash[:notice] = Auth.account_deleted_message
|
46
|
+
redirect_back_or_default Auth.default_destination
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def find_user_model
|
51
|
+
# password fields are protected attrs, so we need to exclude them then add them explicitly.
|
52
|
+
self.model_instance = current_user ||
|
53
|
+
returning(model_class.new(model_params.without(:password, :password_confirmation))) { |model|
|
54
|
+
model.password = model_params[:password]
|
55
|
+
model.password_confirmation = model_params[:password_confirmation]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Uncomment if you don't trust the params[:model] set up by Sparkly routing, or if you've
|
60
|
+
# disabled them.
|
61
|
+
#
|
62
|
+
#def model_name
|
63
|
+
# <%=model.name.inspect%>
|
64
|
+
#end
|
65
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
I'll assume you know what Sparkly Authentication is since it seems
|
2
|
+
to be installed. If that assumption is incorrect, you should check
|
3
|
+
out the Sparkly Authentication readme instead.
|
4
|
+
|
5
|
+
So let's get right into usage. This text was generated by the Sparkly
|
6
|
+
command-line generator, invoked via:
|
7
|
+
|
8
|
+
script/generate sparkly help
|
9
|
+
|
10
|
+
Depending on what arguments are attached, this generator is capable
|
11
|
+
of producing various different results. So let's go through them one
|
12
|
+
at a time, in the most common order...
|
13
|
+
|
14
|
+
0. Usually the first thing you'll want to do is generate the models
|
15
|
+
which will actually be authenticated, such as a User model. See
|
16
|
+
the Rails Guides for more details on that. You don't need to
|
17
|
+
actually run the migrations yet, however.
|
18
|
+
|
19
|
+
1. After you know which models will be authenticated, you're ready
|
20
|
+
to invoke the Sparkly Config generator:
|
21
|
+
|
22
|
+
script/generate sparkly config
|
23
|
+
|
24
|
+
This will generate a Rails Initializer in config/initializers that
|
25
|
+
will be used to set up Sparkly during runtime. This tells it what
|
26
|
+
encryption type to use, which models to authenticate, and so on.
|
27
|
+
You should take a look at this file to make sure the configuration
|
28
|
+
is what you are expecting. Do that. Now.
|
29
|
+
|
30
|
+
2. You also need to generate the database table which stores the
|
31
|
+
password information -- I'm talking about migrations!
|
32
|
+
|
33
|
+
script/generate sparkly migrations
|
34
|
+
|
35
|
+
4. Run the server and try it out. See how things feel. If you want
|
36
|
+
more control over the views (and you should), you can generate
|
37
|
+
them like so:
|
38
|
+
|
39
|
+
script/generate sparkly views
|
40
|
+
|
41
|
+
Their final resting place basically depends on what your Sparkly
|
42
|
+
config from Step 1 looks like. So I hope you double checked it.
|
43
|
+
|
44
|
+
5. Finally, if you need control over the, er, controllers, you can go
|
45
|
+
ahead and generate them like so:
|
46
|
+
|
47
|
+
script/generate sparkly controllers
|
48
|
+
|
49
|
+
Note that exactly which controllers and how many of them will be
|
50
|
+
generated depends, once again, on your Sparkly config. Note also
|
51
|
+
that this will generate the corresponding views for you, so you
|
52
|
+
can skip step 4 if you already know you need to customize the
|
53
|
+
controllers.
|
54
|
+
|
55
|
+
This file has been saved to doc/sparkly_authentication.txt for your
|
56
|
+
reference.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# This file sets up Sparkly Auth to work properly with Rails. It was generated
|
2
|
+
# by "script/generate sparkly config" and can be regenerated with that command, though
|
3
|
+
# you may not want to actually do that if you've made changes to this file.
|
4
|
+
#
|
5
|
+
# You are also HIGHLY encouraged to check out the Auth::Configuration class documentation
|
6
|
+
# for a list of all the options you can set here. There are a LOT of them.
|
7
|
+
#
|
8
|
+
Auth.configure do |config|
|
9
|
+
config.authenticate :user
|
10
|
+
# Adds a model to be authenticated. See the Auth::Model class for information on
|
11
|
+
# what options you can pass. Here are some common examples:
|
12
|
+
#
|
13
|
+
# config.authenticate :user, :accounts_controller => "users", :sessions_controller => "user_sessions"
|
14
|
+
# config.authenticate :user, :key => "login"
|
15
|
+
#
|
16
|
+
# By default, :key is "email" and the controllers are Sparkly's internal controllers.
|
17
|
+
# (Don't forget you can also script/generate controllers or script/generate views to
|
18
|
+
# remove the overhead of setting up your own.)
|
19
|
+
#
|
20
|
+
|
21
|
+
# You can also configure the various behaviors (as long as they support configurations):
|
22
|
+
# config.remember_me.token_theft_message =
|
23
|
+
# "Your account may have been hijacked recently! Verify that all settings are correct."
|
24
|
+
#
|
25
|
+
# config.remember_me.duration = 6.months
|
26
|
+
#
|
27
|
+
# See the class documentation for the behaviors' configurations themselves for details
|
28
|
+
# about these options. (For example, see Auth::Behaviors::RememberMe::Configuration for
|
29
|
+
# the Remember Me configuration options.)
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateSparklyPasswords < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :passwords do |t|
|
4
|
+
t.string :secret
|
5
|
+
t.string :salt
|
6
|
+
|
7
|
+
t.string :persistence_token # the token stored in cookies to persist the user's session
|
8
|
+
t.string :single_access_token # used to authenticate a user for a single request. This is not persisted.
|
9
|
+
t.string :perishable_token # used in confirming an account, usually via email
|
10
|
+
|
11
|
+
t.references :authenticatable, :polymorphic => true
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :passwords
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateSparklyRememberedTokens < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :remembrance_tokens do |t|
|
4
|
+
t.string :series_token
|
5
|
+
t.string :remembrance_token
|
6
|
+
|
7
|
+
t.references :authenticatable, :polymorphic => true
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :remembrance_tokens
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class <%=model.sessions_controller.camelize%>Controller < SparklyController
|
2
|
+
# GET new_model_session_url
|
3
|
+
def new
|
4
|
+
end
|
5
|
+
|
6
|
+
# POST model_session_url
|
7
|
+
def create
|
8
|
+
if session[:locked_out_at] && session[:locked_out_at] > Auth.account_lock_duration.ago
|
9
|
+
flash[:error] = Auth.account_locked_message
|
10
|
+
render :action => 'new'
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
model = model_class.find(:first, :conditions => { model_config.key => model_params[model_config.key] },
|
15
|
+
:include => :passwords)
|
16
|
+
|
17
|
+
if model && model.password_matches?(model_params[:password])
|
18
|
+
login! model
|
19
|
+
redirect_back_or_default Auth.default_destination, Auth.login_successful_message
|
20
|
+
else
|
21
|
+
session[:login_failures] = session[:login_failures].to_i + 1
|
22
|
+
if Auth.max_login_failures && session[:login_failures] >= Auth.max_login_failures
|
23
|
+
session[:locked_out_at] = Time.now
|
24
|
+
flash[:error] = Auth.account_locked_message
|
25
|
+
else
|
26
|
+
flash[:error] = Auth.invalid_credentials_message
|
27
|
+
end
|
28
|
+
render :action => "new"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# DELETE model_session_url
|
33
|
+
def destroy
|
34
|
+
logout!
|
35
|
+
redirect_back_or_default Auth.default_destination, Auth.logout_message
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
# Uncomment if you don't trust the params[:model] set up by Sparkly routing, or if you've
|
40
|
+
# disabled them.
|
41
|
+
#
|
42
|
+
#def model_name
|
43
|
+
# <%=model.name.inspect%>
|
44
|
+
#end
|
45
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'auth/tasks/migrations'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%form_for model, :url => model_path do |f|%>
|
2
|
+
<p>
|
3
|
+
<%=f.error_messages%>
|
4
|
+
</p>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<%=f.label model_config.key%><br/>
|
8
|
+
<%=f.text_field model_config.key%>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<%=f.label :password%><br/>
|
13
|
+
<%=f.password_field :password, :value => ''%>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<p>
|
17
|
+
<%=f.label :password_confirmation%><br/>
|
18
|
+
<%=f.password_field :password_confirmation, :value => ''%>
|
19
|
+
</p>
|
20
|
+
|
21
|
+
<p>
|
22
|
+
<%=f.submit "Update Profile"%>
|
23
|
+
</p>
|
24
|
+
<%end%>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%form_for model, :url => model_path do |f|%>
|
2
|
+
<p>
|
3
|
+
<%=f.error_messages%>
|
4
|
+
</p>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<%=f.label model_config.key%><br/>
|
8
|
+
<%=f.text_field model_config.key%>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<%=f.label :password%><br/>
|
13
|
+
<%=f.password_field :password, :value => ''%>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<p>
|
17
|
+
<%=f.label :password_confirmation%><br/>
|
18
|
+
<%=f.password_field :password_confirmation, :value => ''%>
|
19
|
+
</p>
|
20
|
+
|
21
|
+
<p>
|
22
|
+
<%=f.submit "Sign up"%>
|
23
|
+
</p>
|
24
|
+
<%end%>
|
File without changes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%form_for model, :url => model_session_path do |f|%>
|
2
|
+
<p>
|
3
|
+
<%=f.label model_config.key%><br/>
|
4
|
+
<%=f.text_field model_config.key%>
|
5
|
+
</p>
|
6
|
+
|
7
|
+
<p>
|
8
|
+
<%=f.label :password%><br/>
|
9
|
+
<%=f.password_field :password, :value => ''%>
|
10
|
+
</p>
|
11
|
+
|
12
|
+
<%if Auth.remember_me.enabled?%>
|
13
|
+
<p>
|
14
|
+
<%=f.check_box :remember_me, :checked => false%>
|
15
|
+
<%=f.label :remember_me%>
|
16
|
+
</p>
|
17
|
+
<%end%>
|
18
|
+
|
19
|
+
<p>
|
20
|
+
<%=f.submit "Sign in"%>
|
21
|
+
</p>
|
22
|
+
<%end%>
|
data/init.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path("../dependencies", __FILE__)
|
2
|
+
require File.expand_path("../lib/auth", __FILE__)
|
3
|
+
|
4
|
+
$LOAD_PATH << Auth.path
|
5
|
+
ActiveSupport::Dependencies.load_paths << Auth.path
|
6
|
+
ActiveSupport::Dependencies.load_once_paths << Auth.path
|
7
|
+
#ActiveSupport::Dependencies.load_once_paths.delete(Auth.path)
|
8
|
+
|
9
|
+
# Kick auth after initialize and do it again before every request in development
|
10
|
+
Rails.configuration.to_prepare do
|
11
|
+
Auth.kick!
|
12
|
+
end
|
13
|
+
|
14
|
+
# FIXME HACK extension to ActiveRecord::Errors to allow error attributes to be renamed. This is
|
15
|
+
# because otherwise we'll end up producing very confusing error messages complaining about :secret,
|
16
|
+
# :encrypted_secret, and so forth when all the user really cares about is :password.
|
17
|
+
class ActiveRecord::Errors
|
18
|
+
def rename_attribute(original_name, new_name)
|
19
|
+
original_name, new_name = original_name.to_s, new_name.to_s
|
20
|
+
return if original_name == new_name
|
21
|
+
|
22
|
+
@errors.each do |attribute, old_errors|
|
23
|
+
if attribute.to_s == original_name
|
24
|
+
original_name = attribute # because some are strings, some are symbols.
|
25
|
+
|
26
|
+
old_errors.each do |error|
|
27
|
+
new_error = error.dup
|
28
|
+
new_error.attribute = new_name
|
29
|
+
unless @errors[new_name] && @errors[new_name].include?(new_error)
|
30
|
+
@errors[new_name] ||= []
|
31
|
+
@errors[new_name] << new_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@errors.delete(original_name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ActiveRecord::Error
|
41
|
+
def ==(other)
|
42
|
+
other.kind_of?(ActiveRecord::Error) && attribute.to_s == other.attribute.to_s && message == other.message
|
43
|
+
end
|
44
|
+
end
|
data/lib/auth.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Auth
|
2
|
+
class << self
|
3
|
+
public :delegate
|
4
|
+
delegate :path, :encryptor, :default_accounts_controller_name, :default_sessions_controller_name,
|
5
|
+
:password_update_frequency, :base_controller, :login_required_message, :logout_required_message,
|
6
|
+
:default_destination, :session_duration, :invalid_credentials_message, :login_successful_message,
|
7
|
+
:logout_message, :session_timeout_message, :default_login_path, :account_deleted_message,
|
8
|
+
:account_created_message, :account_updated_message, :account_locked_message, :max_login_failures,
|
9
|
+
:generate_routes?, :disable_route_generation!, :password_uniqueness_message,
|
10
|
+
:password_history_length, :base_controller_name, :account_lock_duration,
|
11
|
+
:password_format, :password_format_message, :minimum_password_length, :behaviors, :behavior_classes,
|
12
|
+
:to => :configuration
|
13
|
+
|
14
|
+
def configuration
|
15
|
+
@configuration ||= Auth::Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def configure
|
19
|
+
yield configuration
|
20
|
+
end
|
21
|
+
|
22
|
+
# Applies all configuration settings. This is done by the Auth system after it has been configured but before
|
23
|
+
# it processes any requests.
|
24
|
+
def configure!
|
25
|
+
begin
|
26
|
+
configuration.apply!
|
27
|
+
rescue NameError
|
28
|
+
puts
|
29
|
+
puts "WARNING: #{$!.message}"
|
30
|
+
puts
|
31
|
+
puts "This happened while trying to configure Sparkly Authentication."
|
32
|
+
puts "You should verify that /config/initializers/sparkly_authentication.rb"
|
33
|
+
puts "is set up properly. It could be that you just haven't created the"
|
34
|
+
puts "model yet. If so, this error will disappear when the model exists."
|
35
|
+
puts
|
36
|
+
if ENV['AUTH_BACKTRACE']
|
37
|
+
puts $!.backtrace
|
38
|
+
else
|
39
|
+
puts "(Run with AUTH_BACKTRACE=true to see a full bactrace.)"
|
40
|
+
end
|
41
|
+
puts
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Useful for cleaning up after tests, but probably not much else.
|
46
|
+
def reset_configuration!
|
47
|
+
@configuration = Auth::Configuration.new
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :kick!, :configure!
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class Auth::Behavior::Base
|
2
|
+
class_inheritable_array :migrations
|
3
|
+
read_inheritable_attribute(:migrations) || write_inheritable_attribute(:migrations, [])
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def apply_to_controllers
|
7
|
+
# Add additional methods to ApplicationController (or whatever controller Sparkly is told to use)
|
8
|
+
Auth.base_controller.send(:include, "#{name}::ControllerExtensions".constantize)
|
9
|
+
# why this doesn't work in cuke?
|
10
|
+
# if (container = self.class).const_defined?(:ControllerExtensions)
|
11
|
+
# Auth.base_controller.send(:include, container.const_get(:ControllerExtensions))
|
12
|
+
# end
|
13
|
+
#rescue NameError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply_to(model)
|
18
|
+
track_behavior(model.target) do
|
19
|
+
apply_to_accounts(model)
|
20
|
+
apply_to_passwords(Password)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply_to_passwords(password_model)
|
25
|
+
raise NotImplementedError, "Be sure to override #apply_to_passwords(passwords_model) in your Auth Behavior"
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply_to_accounts(model_config)
|
29
|
+
raise NotImplementedError, "Be sure to override #apply_to_accounts(model_config) in your Auth Behavior"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def track_behavior(model)
|
34
|
+
if !behavior_tracked?(model)
|
35
|
+
track_behavior!(model)
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def behavior_tracked?(model)
|
41
|
+
behavior_tracker(model).include? behavior_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def track_behavior!(model)
|
45
|
+
behavior_tracker(model) << behavior_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def behavior_tracker(model)
|
49
|
+
model.instance_variable_get("@__behavior_tracker") || model.instance_variable_set("@__behavior_tracker", [])
|
50
|
+
end
|
51
|
+
|
52
|
+
def behavior_name
|
53
|
+
self.class.name
|
54
|
+
end
|
55
|
+
|
56
|
+
public
|
57
|
+
class << self
|
58
|
+
# Declares a migration template for a behavior. If sourcedir is given, it will be used as the location
|
59
|
+
# in which to find the template.
|
60
|
+
def migration(filename)
|
61
|
+
migrations << filename unless migrations.include?(filename)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|