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