super_finder 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +211 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/assets/images/boxy-ne.png +0 -0
- data/assets/images/boxy-nw.png +0 -0
- data/assets/images/boxy-se.png +0 -0
- data/assets/images/boxy-sw.png +0 -0
- data/assets/images/close.png +0 -0
- data/assets/images/magnify.png +0 -0
- data/assets/javascripts/boxy.js +570 -0
- data/assets/javascripts/super_finder.js +193 -0
- data/assets/stylesheets/boxy.css +49 -0
- data/assets/stylesheets/super_finder.css +60 -0
- data/init.rb +2 -0
- data/install.rb +18 -0
- data/lib/super_finder.rb +17 -0
- data/lib/super_finder/cache_manager.rb +38 -0
- data/lib/super_finder/cache_sweeper.rb +41 -0
- data/lib/super_finder/config.rb +45 -0
- data/lib/super_finder/filters.rb +33 -0
- data/lib/super_finder/generator_controller.rb +94 -0
- data/lib/super_finder/helper.rb +18 -0
- data/lib/super_finder/initializer.rb +31 -0
- data/lib/super_finder/routes.rb +10 -0
- data/spec/assets/locales/en.yml +4 -0
- data/spec/functional/cache_spec.rb +45 -0
- data/spec/functional/controller_spec.rb +61 -0
- data/spec/functional/functional_spec_helper.rb +82 -0
- data/spec/functional/routes_spec.rb +29 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/unit/cache_manager_spec.rb +41 -0
- data/spec/unit/config_spec.rb +24 -0
- data/spec/unit/controller_spec.rb +76 -0
- data/spec/unit/initializer_spec.rb +44 -0
- data/spec/unit/unit_spec_helper.rb +1 -0
- data/super_finder.gemspec +92 -0
- data/tasks/super_finder_tasks.rake +4 -0
- data/uninstall.rb +1 -0
- metadata +124 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# Monkey patch to enable filters even in development environment.
|
2
|
+
module SuperFinder
|
3
|
+
|
4
|
+
module Filters
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def enable_super_finder_filters
|
13
|
+
class_eval <<-EOV
|
14
|
+
include SuperFinder::Filters::InstanceMethods
|
15
|
+
before_filter :apply_superfinder_filters
|
16
|
+
EOV
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
|
23
|
+
def apply_superfinder_filters
|
24
|
+
(SuperFinder::Config.instance.before_filters || []).each do |filter|
|
25
|
+
return false if self.send(filter.to_sym) == false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class SuperFinder::GeneratorController < ApplicationController
|
2
|
+
|
3
|
+
unloadable
|
4
|
+
|
5
|
+
# enable_super_finder_filters # hack for dev env
|
6
|
+
|
7
|
+
def index
|
8
|
+
render :text => "SuperFinderResources = #{self.generate.to_json}"
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def reloaded_entry_klass(klass)
|
14
|
+
Rails.env.development? ? eval(klass.name) : klass
|
15
|
+
rescue
|
16
|
+
klass
|
17
|
+
end
|
18
|
+
|
19
|
+
def collect_entries_for(options)
|
20
|
+
SuperFinder::CacheManager.instance.fetch(options[:klass], get_scoper(options)) do
|
21
|
+
entries = (case options[:finder]
|
22
|
+
when Proc
|
23
|
+
options[:finder].call(self)
|
24
|
+
else
|
25
|
+
scoper = SuperFinder::Config.instance.scoper
|
26
|
+
|
27
|
+
conditions = {}
|
28
|
+
|
29
|
+
if options[:scope].nil?
|
30
|
+
if scoper && scoper[:column] # looking for a global scope
|
31
|
+
conditions = { scoper[:column] => self.send(scoper[:getter]).id }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
reloaded_entry_klass(options[:klass]).all(:conditions => conditions)
|
36
|
+
end)
|
37
|
+
|
38
|
+
entries.map do |entry|
|
39
|
+
{
|
40
|
+
:value => column_value(entry, options),
|
41
|
+
:url => entry_url(entry, options)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_scoper(options)
|
48
|
+
self.send(SuperFinder::Config.instance.scoper[:getter].to_sym) rescue nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def label_name(options)
|
52
|
+
case options[:label]
|
53
|
+
when String, Symbol then options[:label].to_s
|
54
|
+
when Proc then options[:label].call(self)
|
55
|
+
else
|
56
|
+
name = options[:klass].name
|
57
|
+
I18n.t(name.demodulize.underscore, :scope => [:activerecord, :models], :default => name.humanize)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def column_value(entry, options)
|
62
|
+
case options[:column]
|
63
|
+
when String, Symbol then entry.attributes[options[:column].to_s]
|
64
|
+
when Proc then options[:column].call(entry).to_s
|
65
|
+
else
|
66
|
+
'[Superfinder] Column is missing'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def entry_url(entry, options)
|
71
|
+
url = options[:url]
|
72
|
+
case url
|
73
|
+
when Hash, nil
|
74
|
+
url = (SuperFinder::Config.instance.url || {}).merge(url || {})
|
75
|
+
resource_name = entry.class.name.pluralize.underscore
|
76
|
+
|
77
|
+
url_for({
|
78
|
+
:controller => File.join(['/', url[:name_prefix], resource_name].compact),
|
79
|
+
:action => url[:action].to_sym ,
|
80
|
+
:id => entry.id
|
81
|
+
})
|
82
|
+
when Proc then url.call(self, entry)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate
|
87
|
+
map = {}
|
88
|
+
SuperFinder::Config.instance.models.each do |options|
|
89
|
+
map[label_name(options)] = self.collect_entries_for(options)
|
90
|
+
end
|
91
|
+
map
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SuperFinder
|
2
|
+
|
3
|
+
module Helper
|
4
|
+
|
5
|
+
def super_finder_tag
|
6
|
+
%{
|
7
|
+
<div id="superfinder" style="display: none">
|
8
|
+
<p>#{text_field_tag 'q'}</p>
|
9
|
+
<ul></ul>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<script src="#{super_finder_resources_url}" type="text/javascript"></script>
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SuperFinder
|
2
|
+
|
3
|
+
class Initializer
|
4
|
+
|
5
|
+
unloadable
|
6
|
+
|
7
|
+
def self.run(&block)
|
8
|
+
block.call(SuperFinder::Config.instance) if block_given?
|
9
|
+
|
10
|
+
raise 'SuperFinder needs one or many models' if (SuperFinder::Config.instance.models || []).empty?
|
11
|
+
|
12
|
+
self.apply
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def self.apply
|
18
|
+
# Cache Sweeper
|
19
|
+
SuperFinder::CacheSweeper.instance # register the observer
|
20
|
+
|
21
|
+
# Before filters
|
22
|
+
unless (filters = SuperFinder::Config.instance.before_filters).empty?
|
23
|
+
SuperFinder::GeneratorController.class_eval do
|
24
|
+
before_filter SuperFinder::Config.instance.before_filters
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/functional_spec_helper')
|
2
|
+
|
3
|
+
describe SuperFinder::CacheSweeper do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
add_project_and_task_records
|
7
|
+
|
8
|
+
SuperFinder::Initializer.run do |config|
|
9
|
+
config.name_prefix = 'admin'
|
10
|
+
config.default_action = :edit
|
11
|
+
config.scoper = {
|
12
|
+
:column => :account_id
|
13
|
+
}
|
14
|
+
config.models = [
|
15
|
+
{ :klass => Project },
|
16
|
+
{ :klass => Task, :scope => false }
|
17
|
+
]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should refresh cache if a record is added' do
|
22
|
+
SuperFinder::CacheSweeper.instance.expects(:refresh_cache!).returns(true)
|
23
|
+
|
24
|
+
Project.create :title => 'Hello world !'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should refresh cache if a record is updated' do
|
28
|
+
SuperFinder::CacheSweeper.instance.expects(:refresh_cache!).returns(true)
|
29
|
+
|
30
|
+
Project.first.update_attribute :title, 'new title'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should refresh cache if a record is destroyed' do
|
34
|
+
SuperFinder::CacheSweeper.instance.expects(:refresh_cache!).returns(true)
|
35
|
+
|
36
|
+
Project.first.destroy
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should refresh cache based on a scope id' do
|
40
|
+
SuperFinder::CacheManager.instance.expects(:refresh!).with(Project, 42).returns(true)
|
41
|
+
|
42
|
+
Project.create :title => 'Hello world !', :account_id => 42
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/functional_spec_helper')
|
2
|
+
|
3
|
+
describe SuperFinder::GeneratorController do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
add_project_and_task_records
|
7
|
+
|
8
|
+
SuperFinder::Initializer.run do |config|
|
9
|
+
config.url = {
|
10
|
+
:name_prefix => 'admin',
|
11
|
+
:action => :edit
|
12
|
+
}
|
13
|
+
config.scoper = {
|
14
|
+
:column => :account_id,
|
15
|
+
:getter => :current_account
|
16
|
+
}
|
17
|
+
config.models = [
|
18
|
+
{ :klass => Project, :column => :title },
|
19
|
+
{ :klass => Task, :column => :name, :label => 'My tasks', :scope => false },
|
20
|
+
{
|
21
|
+
:klass => Person,
|
22
|
+
:column => :nickname,
|
23
|
+
:label => Proc.new { |c| "My people" },
|
24
|
+
:finder => Proc.new { |c| Person.all(:conditions => { :account_id => c.send(:current_account).id }) }
|
25
|
+
}
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
# disable caching
|
30
|
+
SuperFinder::CacheManager.instance.instance_eval do
|
31
|
+
def fetch(klass, scoper = nil, &block)
|
32
|
+
block.call
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@controller = SuperFinder::GeneratorController.new
|
37
|
+
@controller.stubs(:url_for).returns('<url>')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should return of records with an url, a label and a name for each entry' do
|
41
|
+
map = @controller.send(:generate)
|
42
|
+
|
43
|
+
map.should_not be_empty
|
44
|
+
map.size.should == 3
|
45
|
+
|
46
|
+
map["My people"].count.should == 1
|
47
|
+
map["Fun project"].count.should == 2
|
48
|
+
map["My tasks"].count.should == 3
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should generate a json output' do
|
52
|
+
@controller.stubs(:url_for).returns('<url>')
|
53
|
+
@controller.stubs(:view_paths).returns(nil)
|
54
|
+
@controller.stubs(:default_template).returns(nil)
|
55
|
+
@controller.response = ActionController::TestResponse.new
|
56
|
+
|
57
|
+
output = @controller.send(:index)
|
58
|
+
output.should match /^SuperFinderResources = \{(.*)\}$/
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
4
|
+
|
5
|
+
ActiveRecord::Migration.verbose = false
|
6
|
+
ActiveRecord::Schema.define(:version => 1) do
|
7
|
+
create_table :projects do |t|
|
8
|
+
t.column :title, :string
|
9
|
+
t.column :account_id, :integer
|
10
|
+
t.column :created_at, :datetime
|
11
|
+
t.column :updated_at, :datetime
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :tasks do |t|
|
15
|
+
t.column :name, :string
|
16
|
+
t.column :created_at, :datetime
|
17
|
+
t.column :updated_at, :datetime
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table :people do |t|
|
21
|
+
t.column :nickname, :string
|
22
|
+
t.column :account_id, :integer
|
23
|
+
t.column :created_at, :datetime
|
24
|
+
t.column :updated_at, :datetime
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Project < ActiveRecord::Base
|
29
|
+
end
|
30
|
+
|
31
|
+
class Task < ActiveRecord::Base
|
32
|
+
end
|
33
|
+
|
34
|
+
class Person < ActiveRecord::Base
|
35
|
+
end
|
36
|
+
|
37
|
+
class Account < ActiveRecord::Base
|
38
|
+
end
|
39
|
+
|
40
|
+
ApplicationController.class_eval do
|
41
|
+
|
42
|
+
include Spec
|
43
|
+
include Spec::Mocks
|
44
|
+
include Spec::Mocks::Methods
|
45
|
+
include Spec::Mocks::ExampleMethods
|
46
|
+
include Spec::Rails::Mocks
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def current_account
|
51
|
+
mock_model(Account, :id => 42) #("Account", :id => 42)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def add_project_and_task_records
|
58
|
+
clean_records
|
59
|
+
|
60
|
+
Project.create :title => 'Ruby on Rails', :account_id => 42
|
61
|
+
Project.create :title => 'Liquid', :account_id => 42
|
62
|
+
Project.create :title => 'CMS', :account_id => 43
|
63
|
+
|
64
|
+
Task.create :name => 'build engines'
|
65
|
+
Task.create :name => 'drink some beer'
|
66
|
+
Task.create :name => 'sleep'
|
67
|
+
|
68
|
+
Person.create :nickname => 'Bart', :account_id => 42
|
69
|
+
Person.create :nickname => 'Homer', :account_id => 43
|
70
|
+
end
|
71
|
+
|
72
|
+
def clean_records
|
73
|
+
Project.destroy_all
|
74
|
+
Task.destroy_all
|
75
|
+
Person.destroy_all
|
76
|
+
end
|
77
|
+
|
78
|
+
# def teardown_db
|
79
|
+
# ActiveRecord::Base.connection.tables.each do |table|
|
80
|
+
# ActiveRecord::Base.connection.drop_table(table)
|
81
|
+
# end
|
82
|
+
# end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe 'Routes' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
ActionController::Routing::Routes.draw do |map|
|
7
|
+
map.resources :foos do |fu|
|
8
|
+
fu.resources :bars
|
9
|
+
end
|
10
|
+
|
11
|
+
SuperFinder::Routes.draw(map)
|
12
|
+
|
13
|
+
map.connect '/:controller/:action/:id'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should add a custom route' do
|
18
|
+
params_from(:get, '/super_finder_resources.js').should == {
|
19
|
+
:controller => 'super_finder/generator',
|
20
|
+
:action => 'index'
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# from rspec-rails
|
25
|
+
def params_from(method, path)
|
26
|
+
ActionController::Routing::Routes.recognize_path(path, :method => method)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'mocha'
|
6
|
+
|
7
|
+
gem 'actionpack'
|
8
|
+
gem 'activerecord', '>= 1.15.4.7794'
|
9
|
+
require 'action_controller'
|
10
|
+
require 'action_controller/test_process.rb'
|
11
|
+
require 'active_support'
|
12
|
+
require 'action_pack'
|
13
|
+
require 'action_view'
|
14
|
+
require 'active_record'
|
15
|
+
require 'active_record/observer'
|
16
|
+
|
17
|
+
require 'spec'
|
18
|
+
require 'spec/mocks'
|
19
|
+
require 'spec/mocks/mock.rb'
|
20
|
+
require 'spec/rails/mocks.rb'
|
21
|
+
require 'spec/autorun'
|
22
|
+
|
23
|
+
I18n.load_path << Dir[File.join(File.dirname(__FILE__), 'assets', 'locales', '*.{rb,yml}') ]
|
24
|
+
I18n.default_locale = :en
|
25
|
+
|
26
|
+
class ApplicationController < ActionController::Base
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'super_finder'
|
30
|
+
|
31
|
+
|
32
|
+
Spec::Runner.configure do |config|
|
33
|
+
config.mock_with :mocha
|
34
|
+
end
|
35
|
+
|
36
|
+
include ActionController::UrlWriter
|
37
|
+
|