Commit 043c351c authored by azul's avatar azul
Browse files

fix castle gates reloading in development

Here's what was going on...

When loading a page after changing a file in development the server
would crash indicating that config/permissions.rb was loaded twice.
Behind the scenes rails was trying to load the constant Group inside
the ContextPagesController. So it first looked up
  ContextPagesController::Group
and then Group itself. Both lookups fired the after_reload callbacks.
This way the callbacks for Group were run twice leading to errors because
duplicate gates were defined.

What are we doing now?...

Now we load config/permissions.rb inside a config.to_prepare block.
Here's what the documentation says about that hook:
```
  to_prepare: Run after the initializers are run for all Railties
  (including the application itself), but before eager loading and
  the middleware stack is built.
  More importantly, will run upon every request in development, but
  only once (during boot-up) in production and test.
```

We also use require_dependency instead of plain require. This way the file
will actually be reloaded when models are reloaded.
parent 88c799bc
......@@ -102,6 +102,14 @@ module Crabgrass
end
end
#
# Reload the permissions when reloading models in development and once
# in production.
# (They monkeypatch the User and Group classes.)
config.to_prepare do
CastleGates.initialize('config/permissions')
end
initializer "crabgrass_page.freeze_pages" do |app|
require 'crabgrass/page/class_registrar'
::PAGES = Crabgrass::Page::ClassRegistrar.proxies.dup.freeze
......
# (this file is prefixed with z_, so it's loaded last)
#if defined?(User)
#
# This needs to be run last, after models are loaded. Sometimes, environment.rb is loaded
# without models getting loaded. Hence, the defined?(User) test around this block.
# It is hackish, but it works.
#
## FIXME: the hack described in the comment above didn't work anymore after moving to rails3.
CastleGates.initialize('config/permissions')
#end
require 'after_reload/load_const_callback'
require 'after_reload/callback_class_const_missing'
#
# globally define an after_reload method
#
module AfterReload
private
def after_reload(const, &block)
LoadConstCallback.add(const, &block)
end
end
Class.instance_eval { include CallbackClassConstMissing }
Object.instance_eval { include AfterReload }
'After Reload' will allow you to register methods that get called whenever rails reloads a model in development mode.
Useful for writing code that modifies models. This way, your modifying code can get called again when it needs to be.
#
# Override default const_missing.
# see ActiveSupport::Dependencies
#
module CallbackClassConstMissing
def const_missing(const_name)
const = super
if const
LoadConstCallback.fire(const)
end
return const
end
end
#
# module to track the callbacks
#
module LoadConstCallback
mattr_accessor :callbacks
self.callbacks = {}
def self.add(model, &block)
model_name = model.to_s
self.callbacks[model_name] ||= []
self.callbacks[model_name] << block
yield model
end
def self.fire(model)
model_name = model.to_s
if self.callbacks[model_name]
self.callbacks[model_name].each do |block|
block.call(model)
end
end
end
end
# Tests have a dummy after_reload implementation
unless Object.private_methods.include? :after_reload
require 'after_reload'
end
module CastleGates
class Engine < ::Rails::Engine
end
......@@ -16,8 +11,10 @@ module CastleGates
#
# CasteGates.initialize('config/permissions')
#
# require_dependency makes sure the file get's reloaded when
# models get reloaded in development.
def self.initialize(path)
require "#{Rails.root}/#{path}"
require_dependency "#{Rails.root}/#{path}"
end
def self.define(&block)
......@@ -25,10 +22,8 @@ module CastleGates
end
def self.castle(model_class, &block)
after_reload(model_class) do |model_class|
model_class.send(:acts_as_castle)
model_class.class_eval(&block)
end
model_class.send(:acts_as_castle)
model_class.class_eval(&block)
end
def self.holder(prefix, name, options=nil, &block)
......
......@@ -260,20 +260,16 @@ class Holder
def self.eval_block(block, options)
if block
if model = options[:model]
after_reload(model) do |model|
model.class_eval &block
end
model.class_eval &block
end
end
end
def self.holder_from_model(options)
model = options[:model]
raise ArgumentError.new unless model.is_a?(Class) && model.ancestors.include?(ActiveRecord::Base)
after_reload(model) do |model|
options[:model].tap do |model|
raise ArgumentError.new unless model?(model)
model.send(:include, CastleGates::ActsAsHolder::InstanceMethods)
end
model
end
def self.holder_from_association(options)
......@@ -283,5 +279,8 @@ class Holder
association
end
def self.model?(model)
model.is_a?(Class) && model.ancestors.include?(ActiveRecord::Base)
end
end
end
......@@ -43,13 +43,6 @@ SHOW_SQL = false
## TEST HELPERS
##
class Object
private
def after_reload(model, &block)
yield model
end
end
['../init', 'setup_db', 'models', 'fixtures'].each do |file|
require_relative file
end
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment