super_finder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +1 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +211 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/assets/images/boxy-ne.png +0 -0
  7. data/assets/images/boxy-nw.png +0 -0
  8. data/assets/images/boxy-se.png +0 -0
  9. data/assets/images/boxy-sw.png +0 -0
  10. data/assets/images/close.png +0 -0
  11. data/assets/images/magnify.png +0 -0
  12. data/assets/javascripts/boxy.js +570 -0
  13. data/assets/javascripts/super_finder.js +193 -0
  14. data/assets/stylesheets/boxy.css +49 -0
  15. data/assets/stylesheets/super_finder.css +60 -0
  16. data/init.rb +2 -0
  17. data/install.rb +18 -0
  18. data/lib/super_finder.rb +17 -0
  19. data/lib/super_finder/cache_manager.rb +38 -0
  20. data/lib/super_finder/cache_sweeper.rb +41 -0
  21. data/lib/super_finder/config.rb +45 -0
  22. data/lib/super_finder/filters.rb +33 -0
  23. data/lib/super_finder/generator_controller.rb +94 -0
  24. data/lib/super_finder/helper.rb +18 -0
  25. data/lib/super_finder/initializer.rb +31 -0
  26. data/lib/super_finder/routes.rb +10 -0
  27. data/spec/assets/locales/en.yml +4 -0
  28. data/spec/functional/cache_spec.rb +45 -0
  29. data/spec/functional/controller_spec.rb +61 -0
  30. data/spec/functional/functional_spec_helper.rb +82 -0
  31. data/spec/functional/routes_spec.rb +29 -0
  32. data/spec/spec.opts +1 -0
  33. data/spec/spec_helper.rb +37 -0
  34. data/spec/unit/cache_manager_spec.rb +41 -0
  35. data/spec/unit/config_spec.rb +24 -0
  36. data/spec/unit/controller_spec.rb +76 -0
  37. data/spec/unit/initializer_spec.rb +44 -0
  38. data/spec/unit/unit_spec_helper.rb +1 -0
  39. data/super_finder.gemspec +92 -0
  40. data/tasks/super_finder_tasks.rake +4 -0
  41. data/uninstall.rb +1 -0
  42. 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,10 @@
1
+ module SuperFinder
2
+
3
+ module Routes
4
+
5
+ def self.draw(map)
6
+ map.super_finder_resources '/super_finder_resources.js', :controller => 'super_finder/generator', :action => 'index'
7
+ end
8
+ end
9
+
10
+ end
@@ -0,0 +1,4 @@
1
+ en:
2
+ activerecord:
3
+ models:
4
+ project: Fun project
@@ -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
@@ -0,0 +1 @@
1
+ --color
@@ -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
+