super_finder 0.0.1
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.
- 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
|
+
|