├── .document ├── .gitignore ├── .hound.yml ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── integration ├── .gitignore ├── .rspec ├── Gemfile ├── README ├── Rakefile ├── app │ ├── controllers │ │ ├── application_controller.rb │ │ ├── autocomplete_controller.rb │ │ ├── formtastics_controller.rb │ │ ├── id_elements_controller.rb │ │ ├── multiple_selections_controller.rb │ │ ├── nested_models_controller.rb │ │ ├── scoped_autocompletes_controller.rb │ │ ├── simple_forms_controller.rb │ │ └── sub_classes_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ ├── autocomplete_helper.rb │ │ ├── error_messages_helper.rb │ │ ├── id_elements_helper.rb │ │ ├── layout_helper.rb │ │ └── sub_classes_helper.rb │ ├── models │ │ ├── address.rb │ │ ├── brand.rb │ │ ├── feature.rb │ │ ├── foreign_brand.rb │ │ └── product.rb │ └── views │ │ ├── autocomplete │ │ └── new.html.haml │ │ ├── formtastics │ │ └── new.html.haml │ │ ├── id_elements │ │ └── new.html.haml │ │ ├── layouts │ │ └── application.html.haml │ │ ├── multiple_selections │ │ └── new.html.haml │ │ ├── nested_models │ │ └── new.html.haml │ │ ├── scoped_autocompletes │ │ └── new.html.haml │ │ ├── simple_forms │ │ └── new.html.haml │ │ └── sub_classes │ │ └── new.html.haml ├── autotest │ └── discover.rb ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── cucumber.yml │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ └── session_store.rb │ ├── locales │ │ └── en.yml │ ├── mongoid.yml │ └── routes.rb ├── db │ ├── migrate │ │ ├── 20101209014338_create_brands.rb │ │ ├── 20101209014355_create_products.rb │ │ ├── 20101209053936_add_type_to_brand.rb │ │ ├── 20110209020136_create_features.rb │ │ ├── 20110423000347_add_state_to_brands.rb │ │ ├── 20110512153732_create_addresses.rb │ │ └── 20110512153811_add_address_id_to_brand.rb │ ├── schema.rb │ └── seeds.rb ├── doc │ └── README_FOR_APP ├── features │ ├── autocomplete.feature │ ├── step_definitions │ │ ├── autocomplete_steps.rb │ │ ├── pickle_steps.rb │ │ └── web_steps.rb │ └── support │ │ ├── env.rb │ │ ├── ext_env.rb │ │ ├── paths.rb │ │ ├── pickle.rb │ │ └── selectors.rb ├── lib │ └── tasks │ │ └── cucumber.rake ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ ├── images │ │ └── rails.png │ ├── javascripts │ │ ├── application.js │ │ ├── autocomplete-rails.js │ │ ├── jquery-1.5.1.min.js │ │ ├── jquery-ui.min.js │ │ ├── jquery.min.js │ │ └── rails.js │ ├── robots.txt │ └── stylesheets │ │ ├── application.css │ │ ├── jquery-ui-1.8.2.custom.css │ │ └── sass │ │ └── application.sass ├── script │ ├── cucumber │ └── rails └── spec │ ├── acceptance │ ├── acceptance_helper.rb │ ├── autocomplete_spec.rb │ └── support │ │ ├── helpers.rb │ │ └── paths.rb │ ├── controllers │ └── formtastics_controller_spec.rb │ ├── spec_helper.rb │ └── views │ └── formtastics │ └── new.html.haml_spec.rb ├── lib ├── assets │ └── javascripts │ │ ├── autocomplete-rails-uncompressed.js │ │ └── autocomplete-rails.js ├── cucumber │ └── autocomplete.rb ├── generators │ └── autocomplete │ │ ├── install_generator.rb │ │ └── uncompressed_generator.rb ├── rails-jquery-autocomplete.rb ├── rails-jquery-autocomplete │ ├── autocomplete.rb │ ├── form_helper.rb │ ├── formtastic.rb │ ├── formtastic_plugin.rb │ ├── orm.rb │ ├── orm │ │ ├── active_record.rb │ │ ├── mongo_mapper.rb │ │ └── mongoid.rb │ ├── rails │ │ └── engine.rb │ ├── simple_form_plugin.rb │ └── version.rb └── steak │ └── autocomplete.rb ├── rails-jquery-autocomplete.gemspec ├── test-unit.yml └── test ├── form_helper_test.rb ├── generators └── autocomplete │ ├── install_generator_test.rb │ └── uncompressed_generator_test.rb ├── lib ├── rails-jquery-autocomplete │ ├── autocomplete_test.rb │ ├── orm │ │ ├── active_record_test.rb │ │ ├── mongo_mapper_test.rb │ │ └── mongoid_test.rb │ └── simple_form_plugin_test.rb └── rails-jquery-autocomplete_test.rb ├── test_helper.rb └── view_test_helper.rb /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## INTELLIJ/RubyMine 2 | .idea/* 3 | .idea/**/* 4 | 5 | ## MAC OS 6 | .DS_Store 7 | .idea 8 | 9 | ## TEXTMATE 10 | *.tmproj 11 | tmtags 12 | 13 | ## EMACS 14 | *~ 15 | \#* 16 | .\#* 17 | 18 | ## VIM 19 | *.swp 20 | *.swo 21 | 22 | ## PROJECT::GENERAL 23 | coverage 24 | rdoc 25 | pkg 26 | tmp 27 | 28 | ## PROJECT::SPECIFIC 29 | .bundle 30 | .rvmrc 31 | .gemrc 32 | Gemfile.lock 33 | 34 | tags 35 | rails-jquery-autocomplete-*.gem 36 | 37 | .ruby-gemset 38 | .ruby-version 39 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | jshint: 2 | ignore_file: .jshintignore -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | lib/assets/javascripts/autocomplete-rails.js -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 14 | //"indent" : 4, // {int} Number of spaces to use for indentation 15 | "latedef" : false, // true: Require variables/functions to be defined before being used 16 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 20 | "plusplus" : false, // true: Prohibit use of `++` & `--` 21 | "quotmark" : false, // Quotation mark consistency: 22 | // false : do nothing (default) 23 | // true : ensure whatever is used is consistent 24 | // "single" : require single quotes 25 | // "double" : require double quotes 26 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 27 | "unused" : true, // true: Require all defined variables be used 28 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode 29 | "trailing" : false, // true: Prohibit trailing whitespaces 30 | "maxparams" : false, // {int} Max number of formal params allowed per function 31 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 32 | "maxstatements" : false, // {int} Max number statements per function 33 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 34 | "maxlen" : false, // {int} Max number of characters per line 35 | 36 | // Relaxing 37 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 38 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 39 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 40 | "eqnull" : false, // true: Tolerate use of `== null` 41 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 42 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 43 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 44 | // (ex: `for each`, multiple try/catch, function expression…) 45 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 46 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 47 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 48 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 49 | "iterator" : false, // true: Tolerate using the `__iterator__` property 50 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 51 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 52 | "laxcomma" : false, // true: Tolerate comma-first style coding 53 | "loopfunc" : false, // true: Tolerate functions being defined in loops 54 | "multistr" : false, // true: Tolerate multi-line strings 55 | "proto" : false, // true: Tolerate using the `__proto__` property 56 | "scripturl" : false, // true: Tolerate script-targeted URLs 57 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 58 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 59 | "sub" : true, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 60 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 61 | "validthis" : false, // true: Tolerate using this in a non-constructor function 62 | 63 | // Environments 64 | "browser" : true, // Web Browser (window, document, etc) 65 | "couch" : false, // CouchDB 66 | "devel" : true, // Development/debugging (alert, confirm, etc) 67 | "dojo" : false, // Dojo Toolkit 68 | "jquery" : true, // jQuery 69 | "mootools" : false, // MooTools 70 | "node" : false, // Node.js 71 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 72 | "prototypejs" : false, // Prototype and Scriptaculous 73 | "rhino" : false, // Rhino 74 | "worker" : false, // Web Workers 75 | "wsh" : false, // Windows Scripting Host 76 | "yui" : false, // Yahoo User Interface 77 | 78 | // Legacy 79 | "nomen" : false, // true: Prohibit dangling `_` in variables 80 | "onevar" : false, // true: Allow only one `var` statement per function 81 | "passfail" : false, // true: Stop on first error 82 | "white" : false, // true: Check against strict whitespace and indentation rules 83 | 84 | // Custom Globals 85 | "globals" : {} // additional predefined global variables 86 | } 87 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.3 3 | - 2.3.1 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | * 1.0.4 4 | * Add data-min-length attribute (alias to min-length attribute) for better consistency. 5 | * Added railsAutocomplete.init trigger 6 | * 1.0.3 7 | * Fixes issue with SQLITE to support schemas 8 | * 1.0.2 9 | * Fixes compatibility with newer versions of Simple Form gem 10 | * Fixes issue of not propagating events and issue of selected value not a string 11 | * Fixes bug where form 'false' values are treated as true due to being 12 | a string value. 13 | * 1.0.1 14 | * Option to enable/disable no matches found labeling 15 | * 1.0.0 16 | * Adds support for Rails 4 17 | 18 | ___ 19 | 20 | Changes from here on are for the rails3-jquery-autocomplete gem release versions 21 | 22 | 23 | * 1.0.11 24 | * mongoid: escape regular expression in search 25 | * When possible, use jQuery .on() rather than .live() 26 | * 1.0.6 Postgres or non-postgres queries are now determined at model level 27 | * 1.0.3 Fixed Formtastic 2.0 + Ruby 1.8.7 compat issue 28 | * 1.0.2 Fixed issue #93, #94 29 | * 1.0.1 Formtastic 2.0 compatibility fix 30 | * 1.0.0 Rails 3.1 asset pipeline support 31 | * 0.9.1 Fixes issues #96 and #32 32 | * 0.9.0 Massive rewrite 33 | * 0.8.0 Compressed JS file 34 | * 0.7.5 Pull request #46 35 | * 0.7.4 Allows Rails 3.1 36 | * 0.7.3 MongoMapper 37 | * 0.7.2 Steak helper 38 | * 0.7.1 Fixed joined scopes (Issue #43) 39 | * 0.7.0 Scopes 40 | * 0.6.6 ILIKE for postgres 41 | * 0.6.5 JS select event 42 | * 0.6.4 Use YAJL instead of JSON 43 | * 0.6.3 SimpleForm plugin 44 | * 0.6.2 Fix Issue #8 45 | * 0.6.1 Allow specifying fully qualified class name for model object as an option to autocomplete 46 | * 0.6.0 JS Code cleanup 47 | * 0.5.1 Add STI support 48 | * 0.5.0 Formtastic support 49 | * 0.4.0 MongoID support 50 | * 0.3.6 Using .live() to put autocomplete on dynamic fields 51 | 52 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard :test do 5 | watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" } 6 | watch(%r{^test/.+_test\.rb$}) 7 | watch('test/test_helper.rb') { "test" } 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 David Padilla 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Status 2 | 3 | 1. This is the officially maintained fork of 4 | [rails3-jquery-autocomplete](http://github.com/crowdint/rails3-jquery-autocomplete) 5 | 2. All new features and support of Rails 4 and above will occur here 6 | going forward. Rails 3 is also supported on this fork; bug fixes and security fixes for Rails 3 will continue until Rails 3 is EOL. 7 | 8 | # rails-jquery-autocomplete 9 | 10 | [![Build Status](https://secure.travis-ci.org/bigtunacan/rails-jquery-autocomplete.png)](http://travis-ci.org/bigtunacan/rails-jquery-autocomplete) [![Gem Version](https://badge.fury.io/rb/rails-jquery-autocomplete.png)](http://badge.fury.io/rb/rails-jquery-autocomplete) 11 | 12 | An easy way to use jQuery's autocomplete with Rails. 13 | 14 | Supports both ActiveRecord, [mongoid](https://github.com/mongodb/mongoid), and [MongoMapper](https://github.com/jnunemaker/mongomapper). 15 | 16 | Works with [Formtastic](http://github.com/justinfrench/formtastic) 17 | and [SimpleForm](https://github.com/plataformatec/simple_form) 18 | 19 | ## ActiveRecord 20 | 21 | You can find a [detailed example](http://github.com/crowdint/rails3-jquery-autocomplete-app) 22 | on how to use this gem with ActiveRecord [here](http://github.com/crowdint/rails3-jquery-autocomplete-app). 23 | 24 | ## MongoID 25 | 26 | You can find a [detailed example](http://github.com/crowdint/rails3-jquery-autocomplete-app/tree/mongoid) 27 | on how to use this gem with MongoID [here](http://github.com/crowdint/rails3-jquery-autocomplete-app/tree/mongoid). (Same thing, different branch) 28 | 29 | ## Before you start 30 | 31 | Make sure your project is using jQuery-UI and the autocomplete widget 32 | before you continue. 33 | 34 | You can find more info about that here: 35 | 36 | * http://jquery.com/ 37 | * http://jqueryui.com/demos/autocomplete/ 38 | * http://github.com/rails/jquery-ujs 39 | 40 | I'd encourage you to understand how to use those 3 amazing tools before attempting to use this gem. 41 | 42 | ## Installing 43 | 44 | Include the gem on your Gemfile 45 | 46 | gem 'rails-jquery-autocomplete' 47 | 48 | Install it 49 | 50 | bundle install 51 | 52 | ### Rails < 4.x.x 53 | 54 | Run the generator 55 | 56 | rails generate autocomplete:install 57 | 58 | And include autocomplete-rails.js on your layouts 59 | 60 | javascript_include_tag "autocomplete-rails.js" 61 | 62 | #### Upgrading from older versions 63 | 64 | If you are upgrading from a previous version, run the generator after installing to replace the javascript file. 65 | 66 | rails generate autocomplete:install 67 | 68 | I'd recommend you do this every time you update to make sure you have the latest JS file. 69 | 70 | #### Uncompressed Javascript file 71 | 72 | If you want to make changes to the JS file, you can install the 73 | uncompressed version by running: 74 | 75 | rails generate autocomplete:uncompressed 76 | 77 | ### Rails 4 and higher 78 | 79 | Just add it to your app/assets/javascripts/application.js file 80 | 81 | //= require jquery 82 | //= require jquery_ujs 83 | //= require jquery-ui/widgets/autocomplete 84 | //= require autocomplete-rails 85 | 86 | ## Usage 87 | 88 | ### Demo Application by [Yifei](https://github.com/yifeiwu) 89 | 90 | A live demo can be seen 91 | 92 | [here](https://rocky-thicket-9286.herokuapp.com/) 93 | 94 | [source](https://github.com/yifeiwu/rails4-autocomplete-demo) 95 | 96 | As a new developer, I had some issues getting this to work by following the documentation. However after trying some things and reading [Yoni Weisbrod](http://www.yoniweisbrod.com/autocomplete-magic-with-rails/)'s blog post, I was able to make the autocomplete work and implement a few useful features. 97 | 98 | ## Some implemented features 99 | 100 | 1. The css has been changed such that the results show up better against the box of suggestions. See `` for details. I obtained this from a gist(forgive me I don't remember who the author is at the moment, please contact me if you do and I'll give credit). Upon mouseover/arrowkey presses, the selection will be highlighted. 101 | 102 | 2. One Yoni's improvements, you can click on a suggested item to submit the search, instead of having to click on the submit button after clicking on the item. This is an example of how to hook onto the *railsAutocomplete.select* event. 103 | 104 | 3. The autocomplete is implemented in the context of a search form with a simple scope search(see the food model and controller). 105 | 106 | ### Model Example 107 | 108 | Assuming you have a Brand model that contains a name attribute: 109 | 110 | class Brand < ActiveRecord::Base 111 | end 112 | 113 | create_table :brand do |t| 114 | t.column :name, :string 115 | end 116 | 117 | ### Controller 118 | 119 | To set up the required action on your controller, all you have to do is call it with the class name and the method 120 | as in the following example: 121 | 122 | class ProductsController < Admin::BaseController 123 | autocomplete :brand, :name 124 | end 125 | 126 | This will create an action _autocomplete_brand_name_ on your controller, don't forget to add it on your routes file 127 | 128 | resources :products do 129 | get :autocomplete_brand_name, :on => :collection 130 | end 131 | 132 | Verify this path using `rake routes`, you will need it later for the **view** section. 133 | 134 | ### Options 135 | 136 | #### :full => true 137 | 138 | By default, the search starts from the beginning of the string you're searching for. If you want to do a full search, set the _full_ parameter to true. 139 | 140 | class ProductsController < Admin::BaseController 141 | autocomplete :brand, :name, :full => true 142 | end 143 | 144 | The following terms would match the query 'un': 145 | 146 | * Luna 147 | * Unacceptable 148 | * Rerun 149 | 150 | #### :full => false (default behavior) 151 | 152 | Only the following terms would match the query 'un': 153 | 154 | * Unacceptable 155 | 156 | #### :limit => 10 (default behavior) 157 | 158 | By default your search result set is limited to the first 10 records. This can be overridden by specifying the limit option. 159 | 160 | #### :extra_data 161 | 162 | By default, your search will only return the required columns from the database needed to populate your form, namely id and the column you are searching (name, in the above example). 163 | 164 | Passing an array of attributes/column names to this option will fetch and return the specified data. 165 | 166 | class ProductsController < Admin::BaseController 167 | autocomplete :brand, :name, :extra_data => [:slogan] 168 | end 169 | 170 | #### :display_value 171 | 172 | If you want to display a different version of what you're looking for, you can use the :display_value option. 173 | 174 | This options receives a method name as the parameter, and that method will be called on the instance when displaying the results. 175 | 176 | class Brand < ActiveRecord::Base 177 | def funky_method 178 | "#{self.name}.camelize" 179 | end 180 | end 181 | 182 | 183 | class ProductsController < Admin::BaseController 184 | autocomplete :brand, :name, :display_value => :funky_method 185 | end 186 | 187 | In the example above, you will search by _name_, but the autocomplete list will display the result of _funky_method_ 188 | 189 | This wouldn't really make much sense unless you use it with the "id_element" attribute. (See below) 190 | 191 | Only the object's id and the column you are searching on will be returned in JSON, so if your display_value method requires another parameter, make sure to fetch it with the :extra_data option 192 | 193 | #### :hstore 194 | 195 | Added option to support searching in hstore columns. 196 | 197 | Pass a hash with two keys: `:method` and `:key` with values: the hstore field name and the key of the hstore to search. 198 | 199 | e.g `autocomplete :feature, :name, :hstore => {:method => 'name_translations', :key => 'en'}` 200 | 201 | 202 | #### :scopes 203 | 204 | Added option to use scopes. Pass scopes in an array. 205 | e.g `:scopes => [:scope1, :scope2]` 206 | 207 | #### :column_name 208 | 209 | By default autocomplete uses method name as column name. Now it can be specified using column_name options 210 | `:column_name => 'name'` 211 | 212 | #### :case_sensitive 213 | 214 | Normally autocomplete performs a case insensitive search. In cases where this is not desirable, or causes too high 215 | a performance penalty, search can be made case sensitive by specifying `:case_sensitive => true` 216 | 217 | #### json encoder 218 | 219 | Autocomplete uses Yajl as JSON encoder/decoder, but you can specify your own 220 | 221 | class ProductsController < Admin::BaseController 222 | autocomplete :brand, :name do |items| 223 | CustomJSON::Encoder.encode(items) 224 | end 225 | end 226 | 227 | ### View 228 | 229 | On your view, all you have to do is include the attribute autocomplete on the text field 230 | using the url to the autocomplete action as the value. 231 | 232 | form_for @product do |f| 233 | f.autocomplete_field :brand_name, autocomplete_brand_name_products_path 234 | end 235 | 236 | This will generate an HTML tag that looks like: 237 | 238 | 239 | 240 | If you are not using a FormBuilder (form_for) or you just want to include an autocomplete field without the form, you can use the 241 | *autocomplete_field_tag* helper. 242 | 243 | form_tag 'some/path' 244 | autocomplete_field_tag 'address', '', address_autocomplete_path, :size => 75 245 | end 246 | 247 | #### Multiple Values Separated by Delimiter 248 | 249 | To generate an autocomplete input field that accepts multiple values separated by a given delimiter, add the `'data-delimiter'` and `:multiple` options: 250 | 251 | form_for @product do |f| 252 | f.autocomplete_field :brand_names, autocomplete_brand_name_products_path, 253 | 'data-delimiter' => ',', :multiple => true 254 | end 255 | 256 | NOTE: Setting the `:multiple` option to `true` will result in the chosen values being submitted as an array. Leaving this option off will result in the values being passed as a single string, with the values separated by your chosen delimiter. 257 | 258 | #### Automatically focus on the first autocompleted item 259 | 260 | To have the first item be automatically focused on when the autocomplete menu is shown, add the `'data-auto-focus'` option and set it to `true`. 261 | 262 | form_for @product do |f| 263 | f.autocomplete_field :brand_names, autocomplete_brand_name_products_path, 264 | 'data-auto-focus' => true 265 | end 266 | 267 | Now your autocomplete code is unobtrusive, Rails style. 268 | 269 | #### Client-side config 270 | 271 | To configure the behaviour if no matches are found, you can set the following options: 272 | 273 | jQuery.railsAutocomplete.options.showNoMatches //default true 274 | jQuery.railsAutocomplete.options.noMatchesLabel //default 'no existing match' 275 | 276 | These will change the behaviour globally. To set them on a single input field use: 277 | 278 | f.autocomplete_field :brand_names, autocomplete_brand_name_products_path, 279 | 'data-showNoMatches' => false 280 | #or 281 | f.autocomplete_field :brand_names, autocomplete_brand_name_products_path, 282 | 'data-noMatchesLabel' => 'no brands found' 283 | 284 | 285 | ### Getting the object id 286 | 287 | If you need to use the id of the selected object, you can use the *id_element* attribute too: 288 | 289 | f.autocomplete_field :brand_name, autocomplete_brand_name_products_path, :id_element => '#some_element' 290 | 291 | This will update the field with id *#some_element with the id of the selected object. The value for this option can be any jQuery selector. 292 | 293 | ### Changing destination element 294 | 295 | If you need to change destination element where the autocomplete box will be appended to, you can use the **:append_to** option which generates a **data-append-to** HTML attribute that is used in jQuery autocomplete as append_to attribute. 296 | 297 | The :append_to option accepts a string containing jQuery selector for destination element: 298 | 299 | f.autocomplete_field :product_name, '/products/autocomplete_product_name', :append_to => "#product_modal" 300 | 301 | The previous example would append the autocomplete box containing suggestions to element jQuery('#product_modal'). 302 | This is very useful on page where you use various z-indexes and you need to append the box to the topmost element, for example using modal window. 303 | 304 | ### Sending extra search fields 305 | 306 | If you want to send extra fields from your form to the search action, 307 | you can use the **:fields** options which generates a **data-autocomplete-fields** 308 | HTML attribute. 309 | 310 | The :fields option accepts a hash where the keys represent the Ajax request 311 | parameter name and the values represent the jQuery selectors to retrieve the 312 | form elements to get the values: 313 | 314 | f.autocomplete_field :product_name, '/products/autocomplete_product_name', :fields => {:brand_id => '#brand_element', :country => '#country_element'} 315 | 316 | class ProductsController < Admin::BaseController 317 | def autocomplete_product_name 318 | term = params[:term] 319 | brand_id = params[:brand_id] 320 | country = params[:country] 321 | products = Product.where('brand = ? AND country = ? AND name LIKE ?', brand_id, country, "%#{term}%").order(:name).all 322 | render :json => products.map { |product| {:id => product.id, :label => product.name, :value => product.name} } 323 | end 324 | end 325 | 326 | ### Getting extra object data 327 | 328 | If you need to extra data about the selected object, you can use the *:update_elements* HTML attribute. 329 | 330 | The :update_elements attribute accepts a hash where the keys represent the object attribute/column data to use to update and the values are jQuery selectors to retrieve the HTML element to update: 331 | 332 | f.autocomplete_field :brand_name, autocomplete_brand_name_products_path, :update_elements => {:id => '#id_element', :slogan => '#some_other_element'} 333 | 334 | class ProductsController < Admin::BaseController 335 | autocomplete :brand, :name, :extra_data => [:slogan] 336 | end 337 | 338 | The previous example would fetch the extra attribute slogan and update jQuery('#some_other_element') with the slogan value. 339 | 340 | ### Running custom code on selection 341 | 342 | A javascript event named *railsAutocomplete.select* is fired on the input field when a value is selected from the autocomplete drop down. If you need to do something more complex than update fields with data, you can hook into this event, like so: 343 | 344 | $('#my_autocomplete_field').bind('railsAutocomplete.select', function(event, data){ 345 | /* Do something here */ 346 | alert(data.item.id); 347 | }); 348 | 349 | ## Formtastic 350 | 351 | If you are using [Formtastic](http://github.com/justinfrench/formtastic), you automatically get the *autocompleted_input* helper on *semantic_form_for*: 352 | 353 | semantic_form_for @product do |f| 354 | f.input :brand_name, :as => :autocomplete, :url => autocomplete_brand_name_products_path 355 | end 356 | 357 | The only difference with the original helper is that you must specify the autocomplete url using the *:url* option. 358 | 359 | ## SimpleForm 360 | 361 | If you want to use it with simple_form, all you have to do is use the 362 | :as option on the input and set the autocomplete path with the :url 363 | option. 364 | 365 | 366 | simple_form_for @product do |form| 367 | form.input :name 368 | form.input :brand_name, :url => autocomplete_brand_name_products_path, :as => :autocomplete 369 | 370 | # Cucumber 371 | 372 | I have created a step to test your autocomplete with Cucumber and Capybara, all you have to do is add the following lines to your *env.rb* file: 373 | 374 | require 'cucumber/autocomplete' 375 | 376 | Then you'll have access to the following step: 377 | 378 | I choose "([^"]*)" in the autocomplete list 379 | 380 | An example on how to use it: 381 | 382 | @javascript 383 | Scenario: Autocomplete 384 | Given the following brands exists: 385 | | name | 386 | | Alpha | 387 | | Beta | 388 | | Gamma | 389 | And I go to the home page 390 | And I fill in "Brand name" with "al" 391 | And I choose "Alpha" in the autocomplete list 392 | Then the "Brand name" field should contain "Alpha" 393 | 394 | I have only tested this using Capybara, no idea if it works with something else, to see it in action, check the [example app](http://github.com/crowdint/rails3-jquery-autocomplete-app). 395 | 396 | # Steak 397 | 398 | I have created a helper to test your autocomplete with Steak and Capybara, all you have to do is add the following lines to your *acceptance_helper.rb* file: 399 | 400 | require 'steak/autocomplete' 401 | 402 | Then you'll have access to the following helper: 403 | 404 | choose_autocomplete_result 405 | 406 | An example on how to use it: 407 | 408 | scenario "Autocomplete" do 409 | lambda do 410 | Brand.create! [ 411 | {:name => "Alpha"}, 412 | {:name => "Beta"}, 413 | {:name => "Gamma"} 414 | ] 415 | end.should change(Brand, :count).by(3) 416 | 417 | visit home_page 418 | fill_in "Brand name", :with => "al" 419 | choose_autocomplete_result "Alpha" 420 | find_field("Brand name").value.should include("Alpha") 421 | end 422 | 423 | I have only tested this using Capybara, no idea if it works with something else. 424 | 425 | # Development 426 | 427 | If you want to make changes to the gem, first install bundler 1.0.0: 428 | 429 | gem install bundler 430 | 431 | And then, install all your dependencies: 432 | 433 | bundle install 434 | 435 | ## Running the test suite 436 | 437 | You need to have an instance of MongoDB running on your computer or all the mongo tests will fail miserably. 438 | 439 | To run all the tests once, simply use 440 | 441 | rake test 442 | 443 | while you're developing, it is recommended that you run 444 | 445 | bundle exec guard 446 | 447 | to have the relevant test run every time you save a file. 448 | 449 | ## Integration tests 450 | 451 | If you make changes or add features to the jQuery part, please make sure 452 | you write a cucumber test for it. 453 | 454 | You can find an example Rails app on the *integration* folder. 455 | 456 | You can run the integration tests with the cucumber command while on the 457 | integration folder: 458 | 459 | cd integration 460 | rake db:migrate 461 | cucumber 462 | 463 | ## Where to test what 464 | 465 | If you're making or tweaking a plugin (such as the formtastic plugin or 466 | simple\_form plugin), check out the simple\_form\_plugin\_test for an 467 | example of how to test it as part of the main `rake test` run. 468 | Historically, plugins like these had been tested (shoddily) as part of 469 | the integration tests. Feel free to remove them from the integration 470 | suite and move them into the main suite. Your tests will run much 471 | faster, and there will be less likelihood of your feature breaking in 472 | the future. Thanks! 473 | 474 | # Thanks to 475 | 476 | Everyone on [this list](https://github.com/crowdint/rails3-jquery-autocomplete/contributors) 477 | 478 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rake/testtask' 5 | 6 | task :default => [:uglify, :test] 7 | 8 | Rake::TestTask.new(:test) do |test| 9 | test.libs << 'lib' << 'test' 10 | test.pattern = 'test/**/*_test.rb' 11 | test.verbose = true 12 | end 13 | 14 | task :uglify do 15 | require 'uglifier' 16 | file_folder = "lib/assets/javascripts" 17 | File.open("#{file_folder}/autocomplete-rails.js", "w") do |f| 18 | f << Uglifier.compile(File.read("#{file_folder}/autocomplete-rails-uncompressed.js")) 19 | end 20 | end 21 | 22 | -------------------------------------------------------------------------------- /integration/.gitignore: -------------------------------------------------------------------------------- 1 | **.sqlite3 2 | log 3 | -------------------------------------------------------------------------------- /integration/.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | -------------------------------------------------------------------------------- /integration/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '~> 3.0.10' 4 | 5 | gem 'annotate' 6 | gem 'capybara' 7 | gem 'cucumber-rails' 8 | gem 'formtastic'#, :path => '../../formtastic' 9 | gem 'rspec-rails' 10 | gem 'steak' 11 | gem 'database_cleaner' 12 | gem 'haml-rails' 13 | gem 'sass' 14 | gem 'jquery-rails' 15 | gem 'launchy' 16 | gem 'nifty-generators' 17 | gem 'pickle' 18 | gem 'rails-jquery-autocomplete', :path => '../' 19 | gem 'simple_form' 20 | gem 'sqlite3-ruby', :require => 'sqlite3' 21 | 22 | # Test Mongo 23 | # gem 'mongoid' 24 | # gem 'bson_ext' 25 | -------------------------------------------------------------------------------- /integration/README: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.find(:all) 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.com/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- controllers 160 | | |-- helpers 161 | | |-- mailers 162 | | |-- models 163 | | `-- views 164 | | `-- layouts 165 | |-- config 166 | | |-- environments 167 | | |-- initializers 168 | | `-- locales 169 | |-- db 170 | |-- doc 171 | |-- lib 172 | | `-- tasks 173 | |-- log 174 | |-- public 175 | | |-- images 176 | | |-- javascripts 177 | | `-- stylesheets 178 | |-- script 179 | |-- test 180 | | |-- fixtures 181 | | |-- functional 182 | | |-- integration 183 | | |-- performance 184 | | `-- unit 185 | |-- tmp 186 | | |-- cache 187 | | |-- pids 188 | | |-- sessions 189 | | `-- sockets 190 | `-- vendor 191 | `-- plugins 192 | 193 | app 194 | Holds all the code that's specific to this particular application. 195 | 196 | app/controllers 197 | Holds controllers that should be named like weblogs_controller.rb for 198 | automated URL mapping. All controllers should descend from 199 | ApplicationController which itself descends from ActionController::Base. 200 | 201 | app/models 202 | Holds models that should be named like post.rb. Models descend from 203 | ActiveRecord::Base by default. 204 | 205 | app/views 206 | Holds the template files for the view that should be named like 207 | weblogs/index.html.erb for the WeblogsController#index action. All views use 208 | eRuby syntax by default. 209 | 210 | app/views/layouts 211 | Holds the template files for layouts to be used with views. This models the 212 | common header/footer method of wrapping views. In your views, define a layout 213 | using the layout :default and create a file named default.html.erb. 214 | Inside default.html.erb, call <% yield %> to render the view using this 215 | layout. 216 | 217 | app/helpers 218 | Holds view helpers that should be named like weblogs_helper.rb. These are 219 | generated for you automatically when using generators for controllers. 220 | Helpers can be used to wrap functionality for your views into methods. 221 | 222 | config 223 | Configuration files for the Rails environment, the routing map, the database, 224 | and other dependencies. 225 | 226 | db 227 | Contains the database schema in schema.rb. db/migrate contains all the 228 | sequence of Migrations for your schema. 229 | 230 | doc 231 | This directory is where your application documentation will be stored when 232 | generated using rake doc:app 233 | 234 | lib 235 | Application specific libraries. Basically, any kind of custom code that 236 | doesn't belong under controllers, models, or helpers. This directory is in 237 | the load path. 238 | 239 | public 240 | The directory available for the web server. Contains subdirectories for 241 | images, stylesheets, and javascripts. Also contains the dispatchers and the 242 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 243 | server. 244 | 245 | script 246 | Helper scripts for automation and generation. 247 | 248 | test 249 | Unit and functional tests along with fixtures. When using the rails generate 250 | command, template test files will be generated for you and placed in this 251 | directory. 252 | 253 | vendor 254 | External libraries that the application depends on. Also includes the plugins 255 | subdirectory. If the app has frozen rails, those gems also go here, under 256 | vendor/rails/. This directory is in the load path. 257 | -------------------------------------------------------------------------------- /integration/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | Integration::Application.load_tasks 8 | -------------------------------------------------------------------------------- /integration/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /integration/app/controllers/autocomplete_controller.rb: -------------------------------------------------------------------------------- 1 | class AutocompleteController < ApplicationController 2 | autocomplete :brand, :name 3 | 4 | def new 5 | @product = Product.new 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /integration/app/controllers/formtastics_controller.rb: -------------------------------------------------------------------------------- 1 | class FormtasticsController < ApplicationController 2 | autocomplete :brand, :name 3 | 4 | def new 5 | @product = Product.new 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /integration/app/controllers/id_elements_controller.rb: -------------------------------------------------------------------------------- 1 | class IdElementsController < ApplicationController 2 | autocomplete :brand, :name 3 | 4 | def new 5 | @product = Product.new 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /integration/app/controllers/multiple_selections_controller.rb: -------------------------------------------------------------------------------- 1 | class MultipleSelectionsController < ApplicationController 2 | autocomplete :brand, :name 3 | 4 | def new 5 | @product = Product.new 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /integration/app/controllers/nested_models_controller.rb: -------------------------------------------------------------------------------- 1 | class NestedModelsController < ApplicationController 2 | autocomplete :feature, :name 3 | 4 | def new 5 | @product = Product.new 6 | @feature = @product.features.build(:name => 'Glowy,') 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /integration/app/controllers/scoped_autocompletes_controller.rb: -------------------------------------------------------------------------------- 1 | class ScopedAutocompletesController < ApplicationController 2 | autocomplete :brand, :name, :scopes => ["active", "with_address"] 3 | 4 | def new 5 | @product = Product.new 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /integration/app/controllers/simple_forms_controller.rb: -------------------------------------------------------------------------------- 1 | class SimpleFormsController < ApplicationController 2 | autocomplete :brand, :name 3 | 4 | def new 5 | @product = Product.new 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /integration/app/controllers/sub_classes_controller.rb: -------------------------------------------------------------------------------- 1 | class SubClassesController < ApplicationController 2 | autocomplete :foreign_brand, :name 3 | 4 | def new 5 | @product = Product.new 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /integration/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /integration/app/helpers/autocomplete_helper.rb: -------------------------------------------------------------------------------- 1 | module AutocompleteHelper 2 | end 3 | -------------------------------------------------------------------------------- /integration/app/helpers/error_messages_helper.rb: -------------------------------------------------------------------------------- 1 | module ErrorMessagesHelper 2 | # Render error messages for the given objects. The :message and :header_message options are allowed. 3 | def error_messages_for(*objects) 4 | options = objects.extract_options! 5 | options[:header_message] ||= "Invalid Fields" 6 | options[:message] ||= "Correct the following errors and try again." 7 | messages = objects.compact.map { |o| o.errors.full_messages }.flatten 8 | unless messages.empty? 9 | content_tag(:div, :class => "error_messages") do 10 | list_items = messages.map { |msg| content_tag(:li, msg) } 11 | content_tag(:h2, options[:header_message]) + content_tag(:p, options[:message]) + content_tag(:ul, list_items.join.html_safe) 12 | end 13 | end 14 | end 15 | 16 | module FormBuilderAdditions 17 | def error_messages(options = {}) 18 | @template.error_messages_for(@object, options) 19 | end 20 | end 21 | end 22 | 23 | ActionView::Helpers::FormBuilder.send(:include, ErrorMessagesHelper::FormBuilderAdditions) 24 | -------------------------------------------------------------------------------- /integration/app/helpers/id_elements_helper.rb: -------------------------------------------------------------------------------- 1 | module IdElementsHelper 2 | end 3 | -------------------------------------------------------------------------------- /integration/app/helpers/layout_helper.rb: -------------------------------------------------------------------------------- 1 | # These helper methods can be called in your template to set variables to be used in the layout 2 | # This module should be included in all views globally, 3 | # to do so you may need to add this line to your ApplicationController 4 | # helper :layout 5 | module LayoutHelper 6 | def title(page_title, show_title = true) 7 | content_for(:title) { h(page_title.to_s) } 8 | @show_title = show_title 9 | end 10 | 11 | def show_title? 12 | @show_title 13 | end 14 | 15 | def stylesheet(*args) 16 | content_for(:head) { stylesheet_link_tag(*args) } 17 | end 18 | 19 | def javascript(*args) 20 | content_for(:head) { javascript_include_tag(*args) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /integration/app/helpers/sub_classes_helper.rb: -------------------------------------------------------------------------------- 1 | module SubClassesHelper 2 | end 3 | -------------------------------------------------------------------------------- /integration/app/models/address.rb: -------------------------------------------------------------------------------- 1 | class Address < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /integration/app/models/brand.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: brands 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) 7 | # created_at :datetime 8 | # updated_at :datetime 9 | # type :string(255) 10 | # state :boolean 11 | # 12 | 13 | class Brand < ActiveRecord::Base 14 | # include Mongoid::Document 15 | scope :active, where(:state => true) 16 | scope :with_address, joins(:address) 17 | 18 | belongs_to :address 19 | # embeds_one :address 20 | end 21 | -------------------------------------------------------------------------------- /integration/app/models/feature.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: features 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) 7 | # product_id :integer 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # 11 | 12 | class Feature < ActiveRecord::Base 13 | end 14 | -------------------------------------------------------------------------------- /integration/app/models/foreign_brand.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: brands 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) 7 | # created_at :datetime 8 | # updated_at :datetime 9 | # type :string(255) 10 | # state :boolean 11 | # 12 | 13 | class ForeignBrand < Brand 14 | 15 | end 16 | -------------------------------------------------------------------------------- /integration/app/models/product.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: products 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) 7 | # brand_name :string(255) 8 | # created_at :datetime 9 | # updated_at :datetime 10 | # 11 | 12 | class Product < ActiveRecord::Base 13 | attr_accessor :brand_id 14 | 15 | has_many :features 16 | accepts_nested_attributes_for :features 17 | end 18 | -------------------------------------------------------------------------------- /integration/app/views/autocomplete/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Autocomplete 2 | 3 | = form_for @product do |form| 4 | %p 5 | = form.label :name 6 | = form.text_field :name 7 | %p 8 | = form.label :brand_name 9 | = form.autocomplete_field :brand_name, autocomplete_autocomplete_brand_name_path 10 | 11 | -------------------------------------------------------------------------------- /integration/app/views/formtastics/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Formtastic Autocomplete 2 | 3 | = semantic_form_for @product do |form| 4 | %p 5 | = form.input :name 6 | %p 7 | = form.input :brand_name, :as => :autocomplete, :url => autocomplete_brand_name_formtastics_path 8 | 9 | 10 | -------------------------------------------------------------------------------- /integration/app/views/id_elements/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Id Element 2 | 3 | = form_for @product do |form| 4 | %p 5 | = form.label :name 6 | = form.text_field :name 7 | %p 8 | = form.label :brand_name 9 | = form.autocomplete_field :brand_name, autocomplete_brand_name_id_elements_path, 'data-id-element' => '#product_brand_id' 10 | %p 11 | = form.label :brand_id 12 | = form.text_field :brand_id 13 | -------------------------------------------------------------------------------- /integration/app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | 4 | %head 5 | %title 6 | = yield(:title) || "Untitled" 7 | %meta{"http-equiv"=>"Content-Type", :content=>"text/html; charset=utf-8"}/ 8 | = stylesheet_link_tag "application", 'jquery-ui-1.8.2.custom' 9 | = javascript_include_tag 'jquery-1.5.1.min.js', 'jquery-ui.min.js', 'rails', 'autocomplete-rails' 10 | = csrf_meta_tag 11 | = yield(:head) 12 | 13 | %body 14 | #container 15 | - flash.each do |name, msg| 16 | = content_tag :div, msg, :id => "flash_#{name}" 17 | 18 | - if show_title? 19 | %h1= yield(:title) 20 | 21 | = yield 22 | -------------------------------------------------------------------------------- /integration/app/views/multiple_selections/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Multiple 2 | 3 | = form_for @product do |form| 4 | %p 5 | = form.label :name 6 | = form.text_field :name 7 | %p 8 | = form.label :brand_name 9 | = form.autocomplete_field :brand_name, autocomplete_brand_name_multiple_selections_path, :"data-delimiter" => ',' 10 | -------------------------------------------------------------------------------- /integration/app/views/nested_models/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Nested Models example 2 | = form_for @product do |form| 3 | %p 4 | = form.label :name 5 | = form.text_field :name 6 | %p 7 | = form.fields_for :features do |feature_form| 8 | = feature_form.label :name, "Feature Name" 9 | = feature_form.autocomplete_field :name, autocomplete_feature_name_nested_models_path, :"data-delimiter" => ',' 10 | = feature_form.text_field :name 11 | 12 | -------------------------------------------------------------------------------- /integration/app/views/scoped_autocompletes/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Scoped Autocomplete 2 | 3 | = form_for @product do |form| 4 | %p 5 | = form.label :name 6 | = form.text_field :name 7 | %p 8 | = form.label :brand_name 9 | = form.autocomplete_field :brand_name, autocomplete_brand_name_scoped_autocompletes_path 10 | 11 | -------------------------------------------------------------------------------- /integration/app/views/simple_forms/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Autocomplete 2 | 3 | = simple_form_for @product do |form| 4 | %p 5 | = form.input :name 6 | %p 7 | = form.input :brand_name, :url => autocomplete_autocomplete_brand_name_path, :as => :autocomplete 8 | 9 | -------------------------------------------------------------------------------- /integration/app/views/sub_classes/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Subclasses 2 | 3 | = form_for @product do |form| 4 | %p 5 | = form.label :name 6 | = form.text_field :name 7 | %p 8 | = form.label :brand_name 9 | = form.autocomplete_field :brand_name, autocomplete_foreign_brand_name_sub_classes_path 10 | -------------------------------------------------------------------------------- /integration/autotest/discover.rb: -------------------------------------------------------------------------------- 1 | Autotest.add_discovery { "rails" } 2 | Autotest.add_discovery { "rspec2" } 3 | -------------------------------------------------------------------------------- /integration/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Integration::Application 5 | -------------------------------------------------------------------------------- /integration/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # If you have a Gemfile, require the gems listed there, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) if defined?(Bundler) 8 | 9 | module Integration 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Custom directories with classes and modules you want to be autoloadable. 16 | # config.autoload_paths += %W(#{config.root}/extras) 17 | 18 | # Only load the plugins named here, in the order given (default is alphabetical). 19 | # :all can be used as a placeholder for all plugins not explicitly named. 20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 21 | 22 | # Activate observers that should always be running. 23 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 24 | 25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 27 | # config.time_zone = 'Central Time (US & Canada)' 28 | 29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 31 | # config.i18n.default_locale = :de 32 | 33 | # JavaScript files you want as :defaults (application.js is always included). 34 | config.action_view.javascript_expansions[:defaults] = %w() 35 | 36 | # Configure the default encoding used in templates for Ruby 1.9. 37 | config.encoding = "utf-8" 38 | 39 | # Configure sensitive parameters which will be filtered from the log file. 40 | config.filter_parameters += [:password] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /integration/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | gemfile = File.expand_path('../../Gemfile', __FILE__) 5 | begin 6 | ENV['BUNDLE_GEMFILE'] = gemfile 7 | require 'bundler' 8 | Bundler.setup 9 | rescue Bundler::GemNotFound => e 10 | STDERR.puts e.message 11 | STDERR.puts "Try running `bundle install`." 12 | exit! 13 | end if File.exist?(gemfile) 14 | -------------------------------------------------------------------------------- /integration/config/cucumber.yml: -------------------------------------------------------------------------------- 1 | <% 2 | rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" 3 | rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" 4 | std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip" 5 | %> 6 | default: <%= std_opts %> features 7 | wip: --tags @wip:3 --wip features 8 | rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip 9 | -------------------------------------------------------------------------------- /integration/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3-ruby (not necessary on OS X Leopard) 3 | development: 4 | adapter: sqlite3 5 | database: db/development.sqlite3 6 | pool: 5 7 | timeout: 5000 8 | 9 | # Warning: The database defined as "test" will be erased and 10 | # re-generated from your development database when you run "rake". 11 | # Do not set this db to the same as development or production. 12 | test: &test 13 | adapter: sqlite3 14 | database: db/test.sqlite3 15 | pool: 5 16 | timeout: 5000 17 | 18 | production: 19 | adapter: sqlite3 20 | database: db/production.sqlite3 21 | pool: 5 22 | timeout: 5000 23 | 24 | cucumber: 25 | <<: *test -------------------------------------------------------------------------------- /integration/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Integration::Application.initialize! 6 | -------------------------------------------------------------------------------- /integration/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Integration::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /integration/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Integration::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | # config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | end 50 | -------------------------------------------------------------------------------- /integration/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Integration::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /integration/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /integration/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /integration/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /integration/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Integration::Application.config.secret_token = '6cfc71a180f0482c7f2896f0ce99929ad1c6343a6eb73db8e09f7bae0d93edbcabaf06def9d1e5ce7dabf2dd3a410f878272d2120660f2e49ed0ef431d7cad30' 8 | -------------------------------------------------------------------------------- /integration/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Integration::Application.config.session_store :cookie_store, :key => '_integration_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Integration::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /integration/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /integration/config/mongoid.yml: -------------------------------------------------------------------------------- 1 | development: 2 | host: localhost 3 | database: integration_development 4 | 5 | test: 6 | host: localhost 7 | database: integration_test 8 | 9 | # set these environment variables on your prod server 10 | production: 11 | host: <%= ENV['MONGOID_HOST'] %> 12 | port: <%= ENV['MONGOID_PORT'] %> 13 | username: <%= ENV['MONGOID_USERNAME'] %> 14 | password: <%= ENV['MONGOID_PASSWORD'] %> 15 | database: <%= ENV['MONGOID_DATABASE'] %> 16 | # slaves: 17 | # - host: slave1.local 18 | # port: 27018 19 | # - host: slave2.local 20 | # port: 27019 21 | -------------------------------------------------------------------------------- /integration/config/routes.rb: -------------------------------------------------------------------------------- 1 | Integration::Application.routes.draw do 2 | 3 | get "formtastics/new" 4 | 5 | root :to => 'autocomplete#new' 6 | match 'autocomplete/autocomplete_brand_name' => 'autocomplete#autocomplete_brand_name' 7 | 8 | resources :products 9 | resources :id_elements do 10 | get :autocomplete_brand_name, :on => :collection 11 | end 12 | 13 | resources :sub_classes do 14 | get :autocomplete_foreign_brand_name, :on => :collection 15 | end 16 | 17 | resources :multiple_selections do 18 | get :autocomplete_brand_name, :on => :collection 19 | end 20 | 21 | resources :nested_models do 22 | get :autocomplete_feature_name, :on => :collection 23 | end 24 | 25 | resources :simple_forms do 26 | get :autocomplete_feature_name, :on => :collection 27 | end 28 | 29 | resources :scoped_autocompletes do 30 | get :autocomplete_brand_name, :on => :collection 31 | end 32 | 33 | resources :formtastics do 34 | get :autocomplete_brand_name, :on => :collection 35 | end 36 | end 37 | #== Route Map 38 | # Generated on 25 Apr 2011 09:55 39 | # 40 | # root /(.:format) {:controller=>"autocomplete", :action=>"new"} 41 | # autocomplete_autocomplete_brand_name /autocomplete/autocomplete_brand_name(.:format) {:controller=>"autocomplete", :action=>"autocomplete_brand_name"} 42 | # products GET /products(.:format) {:controller=>"products", :action=>"index"} 43 | # POST /products(.:format) {:controller=>"products", :action=>"create"} 44 | # new_product GET /products/new(.:format) {:controller=>"products", :action=>"new"} 45 | # edit_product GET /products/:id/edit(.:format) {:controller=>"products", :action=>"edit"} 46 | # product GET /products/:id(.:format) {:controller=>"products", :action=>"show"} 47 | # PUT /products/:id(.:format) {:controller=>"products", :action=>"update"} 48 | # DELETE /products/:id(.:format) {:controller=>"products", :action=>"destroy"} 49 | # autocomplete_brand_name_id_elements GET /id_elements/autocomplete_brand_name(.:format) {:controller=>"id_elements", :action=>"autocomplete_brand_name"} 50 | # id_elements GET /id_elements(.:format) {:controller=>"id_elements", :action=>"index"} 51 | # POST /id_elements(.:format) {:controller=>"id_elements", :action=>"create"} 52 | # new_id_element GET /id_elements/new(.:format) {:controller=>"id_elements", :action=>"new"} 53 | # edit_id_element GET /id_elements/:id/edit(.:format) {:controller=>"id_elements", :action=>"edit"} 54 | # id_element GET /id_elements/:id(.:format) {:controller=>"id_elements", :action=>"show"} 55 | # PUT /id_elements/:id(.:format) {:controller=>"id_elements", :action=>"update"} 56 | # DELETE /id_elements/:id(.:format) {:controller=>"id_elements", :action=>"destroy"} 57 | # autocomplete_foreign_brand_name_sub_classes GET /sub_classes/autocomplete_foreign_brand_name(.:format) {:controller=>"sub_classes", :action=>"autocomplete_foreign_brand_name"} 58 | # sub_classes GET /sub_classes(.:format) {:controller=>"sub_classes", :action=>"index"} 59 | # POST /sub_classes(.:format) {:controller=>"sub_classes", :action=>"create"} 60 | # new_sub_class GET /sub_classes/new(.:format) {:controller=>"sub_classes", :action=>"new"} 61 | # edit_sub_class GET /sub_classes/:id/edit(.:format) {:controller=>"sub_classes", :action=>"edit"} 62 | # sub_class GET /sub_classes/:id(.:format) {:controller=>"sub_classes", :action=>"show"} 63 | # PUT /sub_classes/:id(.:format) {:controller=>"sub_classes", :action=>"update"} 64 | # DELETE /sub_classes/:id(.:format) {:controller=>"sub_classes", :action=>"destroy"} 65 | # autocomplete_brand_name_multiple_selections GET /multiple_selections/autocomplete_brand_name(.:format) {:controller=>"multiple_selections", :action=>"autocomplete_brand_name"} 66 | # multiple_selections GET /multiple_selections(.:format) {:controller=>"multiple_selections", :action=>"index"} 67 | # POST /multiple_selections(.:format) {:controller=>"multiple_selections", :action=>"create"} 68 | # new_multiple_selection GET /multiple_selections/new(.:format) {:controller=>"multiple_selections", :action=>"new"} 69 | # edit_multiple_selection GET /multiple_selections/:id/edit(.:format) {:controller=>"multiple_selections", :action=>"edit"} 70 | # multiple_selection GET /multiple_selections/:id(.:format) {:controller=>"multiple_selections", :action=>"show"} 71 | # PUT /multiple_selections/:id(.:format) {:controller=>"multiple_selections", :action=>"update"} 72 | # DELETE /multiple_selections/:id(.:format) {:controller=>"multiple_selections", :action=>"destroy"} 73 | # autocomplete_feature_name_nested_models GET /nested_models/autocomplete_feature_name(.:format) {:controller=>"nested_models", :action=>"autocomplete_feature_name"} 74 | # nested_models GET /nested_models(.:format) {:controller=>"nested_models", :action=>"index"} 75 | # POST /nested_models(.:format) {:controller=>"nested_models", :action=>"create"} 76 | # new_nested_model GET /nested_models/new(.:format) {:controller=>"nested_models", :action=>"new"} 77 | # edit_nested_model GET /nested_models/:id/edit(.:format) {:controller=>"nested_models", :action=>"edit"} 78 | # nested_model GET /nested_models/:id(.:format) {:controller=>"nested_models", :action=>"show"} 79 | # PUT /nested_models/:id(.:format) {:controller=>"nested_models", :action=>"update"} 80 | # DELETE /nested_models/:id(.:format) {:controller=>"nested_models", :action=>"destroy"} 81 | # autocomplete_feature_name_simple_forms GET /simple_forms/autocomplete_feature_name(.:format) {:controller=>"simple_forms", :action=>"autocomplete_feature_name"} 82 | # simple_forms GET /simple_forms(.:format) {:controller=>"simple_forms", :action=>"index"} 83 | # POST /simple_forms(.:format) {:controller=>"simple_forms", :action=>"create"} 84 | # new_simple_form GET /simple_forms/new(.:format) {:controller=>"simple_forms", :action=>"new"} 85 | # edit_simple_form GET /simple_forms/:id/edit(.:format) {:controller=>"simple_forms", :action=>"edit"} 86 | # simple_form GET /simple_forms/:id(.:format) {:controller=>"simple_forms", :action=>"show"} 87 | # PUT /simple_forms/:id(.:format) {:controller=>"simple_forms", :action=>"update"} 88 | # DELETE /simple_forms/:id(.:format) {:controller=>"simple_forms", :action=>"destroy"} 89 | # autocomplete_brand_name_scoped_autocompletes GET /scoped_autocompletes/autocomplete_brand_name(.:format) {:controller=>"scoped_autocompletes", :action=>"autocomplete_brand_name"} 90 | # scoped_autocompletes GET /scoped_autocompletes(.:format) {:controller=>"scoped_autocompletes", :action=>"index"} 91 | # POST /scoped_autocompletes(.:format) {:controller=>"scoped_autocompletes", :action=>"create"} 92 | # new_scoped_autocomplete GET /scoped_autocompletes/new(.:format) {:controller=>"scoped_autocompletes", :action=>"new"} 93 | # edit_scoped_autocomplete GET /scoped_autocompletes/:id/edit(.:format) {:controller=>"scoped_autocompletes", :action=>"edit"} 94 | # scoped_autocomplete GET /scoped_autocompletes/:id(.:format) {:controller=>"scoped_autocompletes", :action=>"show"} 95 | # PUT /scoped_autocompletes/:id(.:format) {:controller=>"scoped_autocompletes", :action=>"update"} 96 | # DELETE /scoped_autocompletes/:id(.:format) {:controller=>"scoped_autocompletes", :action=>"destroy"} 97 | -------------------------------------------------------------------------------- /integration/db/migrate/20101209014338_create_brands.rb: -------------------------------------------------------------------------------- 1 | class CreateBrands < ActiveRecord::Migration 2 | def self.up 3 | create_table :brands do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | 10 | def self.down 11 | drop_table :brands 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /integration/db/migrate/20101209014355_create_products.rb: -------------------------------------------------------------------------------- 1 | class CreateProducts < ActiveRecord::Migration 2 | def self.up 3 | create_table :products do |t| 4 | t.string :name 5 | t.string :brand_name 6 | 7 | t.timestamps 8 | end 9 | end 10 | 11 | def self.down 12 | drop_table :products 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /integration/db/migrate/20101209053936_add_type_to_brand.rb: -------------------------------------------------------------------------------- 1 | class AddTypeToBrand < ActiveRecord::Migration 2 | def self.up 3 | add_column :brands, :type, :string 4 | end 5 | 6 | def self.down 7 | remove_column :brands, :type 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /integration/db/migrate/20110209020136_create_features.rb: -------------------------------------------------------------------------------- 1 | class CreateFeatures < ActiveRecord::Migration 2 | def self.up 3 | create_table :features do |t| 4 | t.string :name 5 | t.integer :product_id 6 | 7 | t.timestamps 8 | end 9 | end 10 | 11 | def self.down 12 | drop_table :features 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /integration/db/migrate/20110423000347_add_state_to_brands.rb: -------------------------------------------------------------------------------- 1 | class AddStateToBrands < ActiveRecord::Migration 2 | def self.up 3 | add_column :brands, :state, :boolean 4 | end 5 | 6 | def self.down 7 | remove_column :brands, :state 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /integration/db/migrate/20110512153732_create_addresses.rb: -------------------------------------------------------------------------------- 1 | class CreateAddresses < ActiveRecord::Migration 2 | def self.up 3 | create_table :addresses do |t| 4 | t.string :street 5 | 6 | t.timestamps 7 | end 8 | end 9 | 10 | def self.down 11 | drop_table :addresses 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /integration/db/migrate/20110512153811_add_address_id_to_brand.rb: -------------------------------------------------------------------------------- 1 | class AddAddressIdToBrand < ActiveRecord::Migration 2 | def self.up 3 | add_column :brands, :address_id, :integer 4 | end 5 | 6 | def self.down 7 | remove_column :brands, :address_id 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /integration/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 20110512153811) do 14 | 15 | create_table "addresses", :force => true do |t| 16 | t.string "street" 17 | t.datetime "created_at" 18 | t.datetime "updated_at" 19 | end 20 | 21 | create_table "brands", :force => true do |t| 22 | t.string "name" 23 | t.datetime "created_at" 24 | t.datetime "updated_at" 25 | t.string "type" 26 | t.boolean "state" 27 | t.integer "address_id" 28 | end 29 | 30 | create_table "features", :force => true do |t| 31 | t.string "name" 32 | t.integer "product_id" 33 | t.datetime "created_at" 34 | t.datetime "updated_at" 35 | end 36 | 37 | create_table "products", :force => true do |t| 38 | t.string "name" 39 | t.string "brand_name" 40 | t.datetime "created_at" 41 | t.datetime "updated_at" 42 | end 43 | 44 | end 45 | -------------------------------------------------------------------------------- /integration/db/seeds.rb: -------------------------------------------------------------------------------- 1 | Brand.create(:name => 'Alpha') 2 | Brand.create(:name => 'Gamma') 3 | Brand.create(:name => 'Omega') 4 | -------------------------------------------------------------------------------- /integration/doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /integration/features/autocomplete.feature: -------------------------------------------------------------------------------- 1 | Feature: Autocomplete 2 | In order to do funky stuff 3 | As a User 4 | I want autocomplete! 5 | 6 | Background: 7 | Given the following brands exists: 8 | | name | state | 9 | | Alpha | true | 10 | | Beta | false | 11 | | Gamma | false | 12 | | Kappa | true | 13 | | Kappler | false | 14 | And the following features exists: 15 | | name | 16 | | Shiny | 17 | | Glowy | 18 | 19 | @javascript 20 | Scenario: Autocomplete 21 | Given I go to the home page 22 | And I fill in "Brand name" with "al" 23 | And I choose "Alpha" in the autocomplete list 24 | Then the "Brand name" field should contain "Alpha" 25 | 26 | @javascript 27 | Scenario: Autocomplete, id_element option 28 | Given I go to the new id element page 29 | And I fill in "Brand name" with "al" 30 | And I choose "Alpha" in the autocomplete list 31 | Then the "Brand name" field should contain "Alpha" 32 | And the "Brand" field should contain the "Alpha" brand id 33 | 34 | @javascript 35 | Scenario: Autocomplete for a sub class 36 | Given the following foreign brands exists: 37 | | name | 38 | | Omega | 39 | Given I go to the new sub class page 40 | And I fill in "Brand name" with "om" 41 | And I choose "Omega" in the autocomplete list 42 | Then the "Brand name" field should contain "Omega" 43 | 44 | @javascript 45 | Scenario: Autocomplete 46 | Given I go to the new multiple selection page 47 | And I send al to "Brand name" 48 | And I choose "Alpha" in the autocomplete list 49 | And I send bet to "Brand name" 50 | And I choose "Beta" in the autocomplete list 51 | Then the "Brand name" field should contain "Alpha,Beta" 52 | 53 | @javascript 54 | Scenario: Autocomplete for Nested Models 55 | Given I go to the new nested model page 56 | When I send sh to "Feature Name" 57 | And I choose "Shiny" in the autocomplete list 58 | Then the "Feature Name" field should contain "Glowy,Shiny" 59 | 60 | @javascript 61 | Scenario: Autocomplete with scope 62 | Given the "Kappa" brand has an address 63 | Given I go to the new scoped autocomplete page 64 | And I fill in "Brand name" with "ka" 65 | And I choose "Kappa" in the autocomplete list 66 | Then the "Brand name" field should contain "Kappa" 67 | 68 | @javascript 69 | Scenario: Autocomplete with Formtastic 70 | Given I go to the new formtastic page 71 | And I fill in "Brand name" with "al" 72 | And I choose "Alpha" in the autocomplete list 73 | Then the "Brand name" field should contain "Alpha" 74 | -------------------------------------------------------------------------------- /integration/features/step_definitions/autocomplete_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^the "([^"]*)" field should contain the "([^"]*)" brand id$/ do |field, name| 2 | brand = Brand.where(:name => name).first 3 | steps %{Then the "#{field}" field should contain "#{brand.id}"} 4 | end 5 | 6 | And /^I send (.*) to "(.*)"$/ do |key, element| 7 | find_field(element).native.send_keys(key) 8 | end 9 | 10 | Given /^the "([^"]*)" brand has an address$/ do |brand_name| 11 | brand = Brand.find_by_name(brand_name) 12 | brand.address = Address.create 13 | brand.save! 14 | end 15 | 16 | -------------------------------------------------------------------------------- /integration/features/step_definitions/pickle_steps.rb: -------------------------------------------------------------------------------- 1 | # this file generated by script/generate pickle 2 | 3 | # create a model 4 | Given(/^#{capture_model} exists?(?: with #{capture_fields})?$/) do |name, fields| 5 | create_model(name, fields) 6 | end 7 | 8 | # create n models 9 | Given(/^(\d+) #{capture_plural_factory} exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields| 10 | count.to_i.times { create_model(plural_factory.singularize, fields) } 11 | end 12 | 13 | # create models from a table 14 | Given(/^the following #{capture_plural_factory} exists?:?$/) do |plural_factory, table| 15 | create_models_from_table(plural_factory, table) 16 | end 17 | 18 | # find a model 19 | Then(/^#{capture_model} should exist(?: with #{capture_fields})?$/) do |name, fields| 20 | find_model!(name, fields) 21 | end 22 | 23 | # not find a model 24 | Then(/^#{capture_model} should not exist(?: with #{capture_fields})?$/) do |name, fields| 25 | find_model(name, fields).should be_nil 26 | end 27 | 28 | # find models with a table 29 | Then(/^the following #{capture_plural_factory} should exists?:?$/) do |plural_factory, table| 30 | find_models_from_table(plural_factory, table).should_not be_any(&:nil?) 31 | end 32 | 33 | # find exactly n models 34 | Then(/^(\d+) #{capture_plural_factory} should exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields| 35 | find_models(plural_factory.singularize, fields).size.should == count.to_i 36 | end 37 | 38 | # assert equality of models 39 | Then(/^#{capture_model} should be #{capture_model}$/) do |a, b| 40 | model!(a).should == model!(b) 41 | end 42 | 43 | # assert model is in another model's has_many assoc 44 | Then(/^#{capture_model} should be (?:in|one of|amongst) #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association| 45 | model!(owner).send(association).should include(model!(target)) 46 | end 47 | 48 | # assert model is not in another model's has_many assoc 49 | Then(/^#{capture_model} should not be (?:in|one of|amongst) #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association| 50 | model!(owner).send(association).should_not include(model!(target)) 51 | end 52 | 53 | # assert model is another model's has_one/belongs_to assoc 54 | Then(/^#{capture_model} should be #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association| 55 | model!(owner).send(association).should == model!(target) 56 | end 57 | 58 | # assert model is not another model's has_one/belongs_to assoc 59 | Then(/^#{capture_model} should not be #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association| 60 | model!(owner).send(association).should_not == model!(target) 61 | end 62 | 63 | # assert model.predicate? 64 | Then(/^#{capture_model} should (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate| 65 | if model!(name).respond_to?("has_#{predicate.gsub(' ', '_')}") 66 | model!(name).should send("have_#{predicate.gsub(' ', '_')}") 67 | else 68 | model!(name).should send("be_#{predicate.gsub(' ', '_')}") 69 | end 70 | end 71 | 72 | # assert not model.predicate? 73 | Then(/^#{capture_model} should not (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate| 74 | if model!(name).respond_to?("has_#{predicate.gsub(' ', '_')}") 75 | model!(name).should_not send("have_#{predicate.gsub(' ', '_')}") 76 | else 77 | model!(name).should_not send("be_#{predicate.gsub(' ', '_')}") 78 | end 79 | end 80 | 81 | # model.attribute.should eql(value) 82 | # model.attribute.should_not eql(value) 83 | Then(/^#{capture_model}'s (\w+) (should(?: not)?) be #{capture_value}$/) do |name, attribute, expectation, expected| 84 | actual_value = model(name).send(attribute) 85 | expectation = expectation.gsub(' ', '_') 86 | 87 | case expected 88 | when 'nil', 'true', 'false' 89 | actual_value.send(expectation, send("be_#{expected}")) 90 | when /^[+-]?[0-9_]+(\.\d+)?$/ 91 | actual_value.send(expectation, eql(expected.to_f)) 92 | else 93 | actual_value.to_s.send(expectation, eql(eval(expected))) 94 | end 95 | end 96 | 97 | # assert size of association 98 | Then /^#{capture_model} should have (\d+) (\w+)$/ do |name, size, association| 99 | model!(name).send(association).size.should == size.to_i 100 | end 101 | -------------------------------------------------------------------------------- /integration/features/step_definitions/web_steps.rb: -------------------------------------------------------------------------------- 1 | # TL;DR: YOU SHOULD DELETE THIS FILE 2 | # 3 | # This file was generated by Cucumber-Rails and is only here to get you a head start 4 | # These step definitions are thin wrappers around the Capybara/Webrat API that lets you 5 | # visit pages, interact with widgets and make assertions about page content. 6 | # 7 | # If you use these step definitions as basis for your features you will quickly end up 8 | # with features that are: 9 | # 10 | # * Hard to maintain 11 | # * Verbose to read 12 | # 13 | # A much better approach is to write your own higher level step definitions, following 14 | # the advice in the following blog posts: 15 | # 16 | # * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html 17 | # * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/ 18 | # * http://elabs.se/blog/15-you-re-cuking-it-wrong 19 | # 20 | 21 | 22 | require 'uri' 23 | require 'cgi' 24 | require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths")) 25 | require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors")) 26 | 27 | module WithinHelpers 28 | def with_scope(locator) 29 | locator ? within(*selector_for(locator)) { yield } : yield 30 | end 31 | end 32 | World(WithinHelpers) 33 | 34 | # Single-line step scoper 35 | When /^(.*) within (.*[^:])$/ do |step, parent| 36 | with_scope(parent) { When step } 37 | end 38 | 39 | # Multi-line step scoper 40 | When /^(.*) within (.*[^:]):$/ do |step, parent, table_or_string| 41 | with_scope(parent) { When "#{step}:", table_or_string } 42 | end 43 | 44 | Given /^(?:|I )am on (.+)$/ do |page_name| 45 | visit path_to(page_name) 46 | end 47 | 48 | When /^(?:|I )go to (.+)$/ do |page_name| 49 | visit path_to(page_name) 50 | end 51 | 52 | When /^(?:|I )press "([^"]*)"$/ do |button| 53 | click_button(button) 54 | end 55 | 56 | When /^(?:|I )follow "([^"]*)"$/ do |link| 57 | click_link(link) 58 | end 59 | 60 | When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value| 61 | fill_in(field, :with => value) 62 | end 63 | 64 | When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field| 65 | fill_in(field, :with => value) 66 | end 67 | 68 | # Use this to fill in an entire form with data from a table. Example: 69 | # 70 | # When I fill in the following: 71 | # | Account Number | 5002 | 72 | # | Expiry date | 2009-11-01 | 73 | # | Note | Nice guy | 74 | # | Wants Email? | | 75 | # 76 | # TODO: Add support for checkbox, select og option 77 | # based on naming conventions. 78 | # 79 | When /^(?:|I )fill in the following:$/ do |fields| 80 | fields.rows_hash.each do |name, value| 81 | When %{I fill in "#{name}" with "#{value}"} 82 | end 83 | end 84 | 85 | When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field| 86 | select(value, :from => field) 87 | end 88 | 89 | When /^(?:|I )check "([^"]*)"$/ do |field| 90 | check(field) 91 | end 92 | 93 | When /^(?:|I )uncheck "([^"]*)"$/ do |field| 94 | uncheck(field) 95 | end 96 | 97 | When /^(?:|I )choose "([^"]*)"$/ do |field| 98 | choose(field) 99 | end 100 | 101 | When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field| 102 | attach_file(field, File.expand_path(path)) 103 | end 104 | 105 | Then /^(?:|I )should see "([^"]*)"$/ do |text| 106 | if page.respond_to? :should 107 | page.should have_content(text) 108 | else 109 | assert page.has_content?(text) 110 | end 111 | end 112 | 113 | Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp| 114 | regexp = Regexp.new(regexp) 115 | 116 | if page.respond_to? :should 117 | page.should have_xpath('//*', :text => regexp) 118 | else 119 | assert page.has_xpath?('//*', :text => regexp) 120 | end 121 | end 122 | 123 | Then /^(?:|I )should not see "([^"]*)"$/ do |text| 124 | if page.respond_to? :should 125 | page.should have_no_content(text) 126 | else 127 | assert page.has_no_content?(text) 128 | end 129 | end 130 | 131 | Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp| 132 | regexp = Regexp.new(regexp) 133 | 134 | if page.respond_to? :should 135 | page.should have_no_xpath('//*', :text => regexp) 136 | else 137 | assert page.has_no_xpath?('//*', :text => regexp) 138 | end 139 | end 140 | 141 | Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value| 142 | with_scope(parent) do 143 | field = find_field(field) 144 | field_value = (field.tag_name == 'textarea') ? field.text : field.value 145 | if field_value.respond_to? :should 146 | field_value.should =~ /#{value}/ 147 | else 148 | assert_match(/#{value}/, field_value) 149 | end 150 | end 151 | end 152 | 153 | Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value| 154 | with_scope(parent) do 155 | field = find_field(field) 156 | field_value = (field.tag_name == 'textarea') ? field.text : field.value 157 | if field_value.respond_to? :should_not 158 | field_value.should_not =~ /#{value}/ 159 | else 160 | assert_no_match(/#{value}/, field_value) 161 | end 162 | end 163 | end 164 | 165 | Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent| 166 | with_scope(parent) do 167 | field_checked = find_field(label)['checked'] 168 | if field_checked.respond_to? :should 169 | field_checked.should be_true 170 | else 171 | assert field_checked 172 | end 173 | end 174 | end 175 | 176 | Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent| 177 | with_scope(parent) do 178 | field_checked = find_field(label)['checked'] 179 | if field_checked.respond_to? :should 180 | field_checked.should be_false 181 | else 182 | assert !field_checked 183 | end 184 | end 185 | end 186 | 187 | Then /^(?:|I )should be on (.+)$/ do |page_name| 188 | current_path = URI.parse(current_url).path 189 | if current_path.respond_to? :should 190 | current_path.should == path_to(page_name) 191 | else 192 | assert_equal path_to(page_name), current_path 193 | end 194 | end 195 | 196 | Then /^(?:|I )should have the following query string:$/ do |expected_pairs| 197 | query = URI.parse(current_url).query 198 | actual_params = query ? CGI.parse(query) : {} 199 | expected_params = {} 200 | expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')} 201 | 202 | if actual_params.respond_to? :should 203 | actual_params.should == expected_params 204 | else 205 | assert_equal expected_params, actual_params 206 | end 207 | end 208 | 209 | Then /^show me the page$/ do 210 | save_and_open_page 211 | end 212 | -------------------------------------------------------------------------------- /integration/features/support/env.rb: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | require 'cucumber/rails' 8 | 9 | # Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In 10 | # order to ease the transition to Capybara we set the default here. If you'd 11 | # prefer to use XPath just remove this line and adjust any selectors in your 12 | # steps to use the XPath syntax. 13 | Capybara.default_selector = :css 14 | 15 | # By default, any exception happening in your Rails application will bubble up 16 | # to Cucumber so that your scenario will fail. This is a different from how 17 | # your application behaves in the production environment, where an error page will 18 | # be rendered instead. 19 | # 20 | # Sometimes we want to override this default behaviour and allow Rails to rescue 21 | # exceptions and display an error page (just like when the app is running in production). 22 | # Typical scenarios where you want to do this is when you test your error pages. 23 | # There are two ways to allow Rails to rescue exceptions: 24 | # 25 | # 1) Tag your scenario (or feature) with @allow-rescue 26 | # 27 | # 2) Set the value below to true. Beware that doing this globally is not 28 | # recommended as it will mask a lot of errors for you! 29 | # 30 | ActionController::Base.allow_rescue = false 31 | 32 | # Remove/comment out the lines below if your app doesn't have a database. 33 | # For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. 34 | begin 35 | DatabaseCleaner.strategy = :truncation 36 | rescue NameError 37 | raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." 38 | end 39 | 40 | # You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. 41 | # See the DatabaseCleaner documentation for details. Example: 42 | # 43 | # Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do 44 | # DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]} 45 | # end 46 | # 47 | # Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do 48 | # DatabaseCleaner.strategy = :transaction 49 | # end 50 | # 51 | -------------------------------------------------------------------------------- /integration/features/support/ext_env.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Require the test autocomplete steps 3 | # 4 | require 'cucumber/autocomplete' 5 | -------------------------------------------------------------------------------- /integration/features/support/paths.rb: -------------------------------------------------------------------------------- 1 | module NavigationHelpers 2 | # Maps a name to a path. Used by the 3 | # 4 | # When /^I go to (.+)$/ do |page_name| 5 | # 6 | # step definition in web_steps.rb 7 | # 8 | def path_to(page_name) 9 | case page_name 10 | 11 | when /^the home\s?page$/ 12 | '/' 13 | 14 | # Add more mappings here. 15 | # Here is an example that pulls values out of the Regexp: 16 | # 17 | # when /^(.*)'s profile page$/i 18 | # user_profile_path(User.find_by_login($1)) 19 | 20 | else 21 | begin 22 | page_name =~ /^the (.*) page$/ 23 | path_components = $1.split(/\s+/) 24 | self.send(path_components.push('path').join('_').to_sym) 25 | rescue NoMethodError, ArgumentError 26 | raise "Can't find mapping from \"#{page_name}\" to a path.\n" + 27 | "Now, go and add a mapping in #{__FILE__}" 28 | end 29 | end 30 | end 31 | end 32 | 33 | World(NavigationHelpers) 34 | -------------------------------------------------------------------------------- /integration/features/support/pickle.rb: -------------------------------------------------------------------------------- 1 | # this file generated by script/generate pickle [paths] [email] 2 | # 3 | # Make sure that you are loading your factory of choice in your cucumber environment 4 | # 5 | # For machinist add: features/support/machinist.rb 6 | # 7 | # require 'machinist/active_record' # or your chosen adaptor 8 | # require File.dirname(__FILE__) + '/../../spec/blueprints' # or wherever your blueprints are 9 | # Before { Sham.reset } # to reset Sham's seed between scenarios so each run has same random sequences 10 | # 11 | # For FactoryGirl add: features/support/factory_girl.rb 12 | # 13 | # require 'factory_girl' 14 | # require File.dirname(__FILE__) + '/../../spec/factories' # or wherever your factories are 15 | # 16 | # You may also need to add gem dependencies on your factory of choice in config/environments/cucumber.rb 17 | 18 | require 'pickle/world' 19 | # Example of configuring pickle: 20 | # 21 | # Pickle.configure do |config| 22 | # config.adapters = [:machinist] 23 | # config.map 'I', 'myself', 'me', 'my', :to => 'user: "me"' 24 | # end 25 | -------------------------------------------------------------------------------- /integration/features/support/selectors.rb: -------------------------------------------------------------------------------- 1 | module HtmlSelectorsHelpers 2 | # Maps a name to a selector. Used primarily by the 3 | # 4 | # When /^(.+) within (.+)$/ do |step, scope| 5 | # 6 | # step definitions in web_steps.rb 7 | # 8 | def selector_for(locator) 9 | case locator 10 | 11 | when "the page" 12 | "html > body" 13 | 14 | # Add more mappings here. 15 | # Here is an example that pulls values out of the Regexp: 16 | # 17 | # when /^the (notice|error|info) flash$/ 18 | # ".flash.#{$1}" 19 | 20 | # You can also return an array to use a different selector 21 | # type, like: 22 | # 23 | # when /the header/ 24 | # [:xpath, "//header"] 25 | 26 | # This allows you to provide a quoted selector as the scope 27 | # for "within" steps as was previously the default for the 28 | # web steps: 29 | when /^"(.+)"$/ 30 | $1 31 | 32 | else 33 | raise "Can't find mapping from \"#{locator}\" to a selector.\n" + 34 | "Now, go and add a mapping in #{__FILE__}" 35 | end 36 | end 37 | end 38 | 39 | World(HtmlSelectorsHelpers) 40 | -------------------------------------------------------------------------------- /integration/lib/tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 2 | # It is recommended to regenerate this file in the future when you upgrade to a 3 | # newer version of cucumber-rails. Consider adding your own code to a new file 4 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 5 | # files. 6 | 7 | 8 | unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks 9 | 10 | vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 11 | $LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? 12 | 13 | begin 14 | require 'cucumber/rake/task' 15 | 16 | namespace :cucumber do 17 | Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| 18 | t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. 19 | t.fork = true # You may get faster startup if you set this to false 20 | t.profile = 'default' 21 | end 22 | 23 | Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| 24 | t.binary = vendored_cucumber_bin 25 | t.fork = true # You may get faster startup if you set this to false 26 | t.profile = 'wip' 27 | end 28 | 29 | Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| 30 | t.binary = vendored_cucumber_bin 31 | t.fork = true # You may get faster startup if you set this to false 32 | t.profile = 'rerun' 33 | end 34 | 35 | desc 'Run all features' 36 | task :all => [:ok, :wip] 37 | 38 | task :statsetup do 39 | require 'rails/code_statistics' 40 | ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') 41 | ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') 42 | end 43 | end 44 | desc 'Alias for cucumber:ok' 45 | task :cucumber => 'cucumber:ok' 46 | 47 | task :default => :cucumber 48 | 49 | task :features => :cucumber do 50 | STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 51 | end 52 | 53 | # In case we don't have ActiveRecord, append a no-op task that we can depend upon. 54 | task 'db:test:prepare' do 55 | end 56 | 57 | task :stats => 'cucumber:statsetup' 58 | rescue LoadError 59 | desc 'cucumber rake task not available (cucumber not installed)' 60 | task :cucumber do 61 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 62 | end 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /integration/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /integration/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /integration/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /integration/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/risuiowa/rails-jquery-autocomplete/4ddcea08dcb5e48806aeed23c7d3e898548a5030/integration/public/favicon.ico -------------------------------------------------------------------------------- /integration/public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/risuiowa/rails-jquery-autocomplete/4ddcea08dcb5e48806aeed23c7d3e898548a5030/integration/public/images/rails.png -------------------------------------------------------------------------------- /integration/public/javascripts/application.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/risuiowa/rails-jquery-autocomplete/4ddcea08dcb5e48806aeed23c7d3e898548a5030/integration/public/javascripts/application.js -------------------------------------------------------------------------------- /integration/public/javascripts/autocomplete-rails.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Unobtrusive autocomplete 3 | * 4 | * To use it, you just have to include the HTML attribute autocomplete 5 | * with the autocomplete URL as the value 6 | * 7 | * Example: 8 | * 9 | * 10 | * Optionally, you can use a jQuery selector to specify a field that can 11 | * be updated with the element id whenever you find a matching value 12 | * 13 | * Example: 14 | * 15 | */ 16 | 17 | (function(jQuery) 18 | { 19 | var self = null; 20 | jQuery.fn.railsAutocomplete = function() { 21 | return this.live('focus',function() { 22 | if (!this.railsAutoCompleter) { 23 | this.railsAutoCompleter = new jQuery.railsAutocomplete(this); 24 | } 25 | }); 26 | }; 27 | 28 | jQuery.railsAutocomplete = function (e) { 29 | _e = e; 30 | this.init(_e); 31 | }; 32 | 33 | jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = { 34 | railsAutocomplete: '0.0.1' 35 | }; 36 | 37 | jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend; 38 | jQuery.railsAutocomplete.fn.extend({ 39 | init: function(e) { 40 | e.delimiter = jQuery(e).attr('data-delimiter') || null; 41 | function split( val ) { 42 | return val.split( e.delimiter ); 43 | } 44 | function extractLast( term ) { 45 | return split( term ).pop().replace(/^\s+/,""); 46 | } 47 | 48 | jQuery(e).autocomplete({ 49 | source: function( request, response ) { 50 | jQuery.getJSON( jQuery(e).attr('data-autocomplete'), { 51 | term: extractLast( request.term ) 52 | }, function() { 53 | jQuery(arguments[0]).each(function(i, el) { 54 | var obj = {}; 55 | obj[el.id] = el; 56 | jQuery(e).data(obj); 57 | }); 58 | response.apply(null, arguments); 59 | }); 60 | }, 61 | search: function() { 62 | // custom minLength 63 | var term = extractLast( this.value ); 64 | if ( term.length < 2 ) { 65 | return false; 66 | } 67 | }, 68 | focus: function() { 69 | // prevent value inserted on focus 70 | return false; 71 | }, 72 | select: function( event, ui ) { 73 | var terms = split( this.value ); 74 | // remove the current input 75 | terms.pop(); 76 | // add the selected item 77 | terms.push( ui.item.value ); 78 | // add placeholder to get the comma-and-space at the end 79 | if (e.delimiter != null) { 80 | terms.push( "" ); 81 | this.value = terms.join( e.delimiter ); 82 | } else { 83 | this.value = terms.join(""); 84 | if (jQuery(this).attr('data-id-element')) { 85 | jQuery(jQuery(this).attr('data-id-element')).val(ui.item.id); 86 | } 87 | if (jQuery(this).attr('data-update-elements')) { 88 | var data = jQuery(this).data(ui.item.id.toString()); 89 | var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements")); 90 | for (var key in update_elements) { 91 | jQuery(update_elements[key]).val(data[key]); 92 | } 93 | } 94 | } 95 | var remember_string = this.value; 96 | jQuery(this).bind('keyup.clearId', function(){ 97 | if(jQuery(this).val().trim() != remember_string.trim()){ 98 | jQuery(jQuery(this).attr('data-id-element')).val(""); 99 | jQuery(this).unbind('keyup.clearId'); 100 | } 101 | }); 102 | jQuery(this).trigger('railsAutocomplete.select', ui); 103 | 104 | return false; 105 | } 106 | }); 107 | } 108 | }); 109 | 110 | jQuery(document).ready(function(){ 111 | jQuery('input[data-autocomplete]').railsAutocomplete(); 112 | }); 113 | })(jQuery); 114 | -------------------------------------------------------------------------------- /integration/public/javascripts/jquery-ui.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI 1.8.9 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI 9 | */ 10 | (function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.9",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106, 11 | NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this, 12 | "position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position"); 13 | if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f, 14 | "border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h, 15 | d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}}); 16 | c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a); 48 | return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;a.target==this._mouseDownEvent.target&&c.data(a.target,this.widgetName+".preventClickEvent", 49 | true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); 50 | ;/* 51 | * jQuery UI Position 1.8.9 52 | * 53 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 54 | * Dual licensed under the MIT or GPL Version 2 licenses. 55 | * http://jquery.org/license 56 | * 57 | * http://docs.jquery.com/UI/Position 58 | */ 59 | (function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY, 60 | left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+= 61 | k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-= 62 | m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left= 63 | d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= 64 | a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), 65 | g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); 66 | ;/* 67 | * jQuery UI Autocomplete 1.8.9 68 | * 69 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 70 | * Dual licensed under the MIT or GPL Version 2 licenses. 71 | * http://jquery.org/license 72 | * 73 | * http://docs.jquery.com/UI/Autocomplete 74 | * 75 | * Depends: 76 | * jquery.ui.core.js 77 | * jquery.ui.widget.js 78 | * jquery.ui.position.js 79 | */ 80 | (function(d){d.widget("ui.autocomplete",{options:{appendTo:"body",delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,f;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){f=false;var e=d.ui.keyCode; 81 | switch(c.keyCode){case e.PAGE_UP:a._move("previousPage",c);break;case e.PAGE_DOWN:a._move("nextPage",c);break;case e.UP:a._move("previous",c);c.preventDefault();break;case e.DOWN:a._move("next",c);c.preventDefault();break;case e.ENTER:case e.NUMPAD_ENTER:if(a.menu.active){f=true;c.preventDefault()}case e.TAB:if(!a.menu.active)return;a.menu.select(c);break;case e.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=a.element.val()){a.selectedItem= 82 | null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(f){f=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};this.menu=d("
    ").addClass("ui-autocomplete").appendTo(d(this.options.appendTo|| 83 | "body",b)[0]).mousedown(function(c){var e=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(g){g.target!==a.element[0]&&g.target!==e&&!d.ui.contains(e,g.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,e){e=e.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:e})&&/^key/.test(c.originalEvent.type)&&a.element.val(e.value)},selected:function(c,e){var g=e.item.data("item.autocomplete"), 84 | h=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=h;setTimeout(function(){a.previous=h;a.selectedItem=g},1)}false!==a._trigger("select",c,{item:g})&&a.element.val(g.value);a.term=a.element.val();a.close(c);a.selectedItem=g},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"); 85 | this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&&b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,f;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,e){e(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source=== 86 | "string"){f=this.options.source;this.source=function(c,e){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:f,data:c,dataType:"json",success:function(g,h,i){i===a.xhr&&e(g);a.xhr=null},error:function(g){g===a.xhr&&e([]);a.xhr=null}})}}else this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length").data("item.autocomplete",b).append(d("").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b); 90 | else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(a,b){var f=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return f.test(c.label||c.value||c)})}})})(jQuery); 91 | (function(d){d.widget("ui.menu",{_create:function(){var a=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(b){if(d(b.target).closest(".ui-menu-item a").length){b.preventDefault();a.select(b)}});this.refresh()},refresh:function(){var a=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", 92 | -1).mouseenter(function(b){a.activate(b,d(this).parent())}).mouseleave(function(){a.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var f=b.offset().top-this.element.offset().top,c=this.element.attr("scrollTop"),e=this.element.height();if(f<0)this.element.attr("scrollTop",c+f);else f>=e&&this.element.attr("scrollTop",c+f-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",a,{item:b})}, 93 | deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,f){if(this.active){a=this.active[a+"All"](".ui-menu-item").eq(0); 94 | a.length?this.activate(f,a):this.activate(f,this.element.children(b))}else this.activate(f,this.element.children(b))},nextPage:function(a){if(this.hasScroll())if(!this.active||this.last())this.activate(a,this.element.children(".ui-menu-item:first"));else{var b=this.active.offset().top,f=this.element.height(),c=this.element.children(".ui-menu-item").filter(function(){var e=d(this).offset().top-b-f+d(this).height();return e<10&&e>-10});c.length||(c=this.element.children(".ui-menu-item:last"));this.activate(a, 95 | c)}else this.activate(a,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(a){if(this.hasScroll())if(!this.active||this.first())this.activate(a,this.element.children(".ui-menu-item:last"));else{var b=this.active.offset().top,f=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-b+f-d(this).height();return c<10&&c>-10});result.length||(result=this.element.children(".ui-menu-item:first")); 96 | this.activate(a,result)}else this.activate(a,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()Delete 60 | function handleMethod(link) { 61 | var href = link.attr('href'), 62 | method = link.attr('data-method'), 63 | csrf_token = $('meta[name=csrf-token]').attr('content'), 64 | csrf_param = $('meta[name=csrf-param]').attr('content'), 65 | form = $('
    '), 66 | metadata_input = ''; 67 | 68 | if (csrf_param !== undefined && csrf_token !== undefined) { 69 | metadata_input += ''; 70 | } 71 | 72 | form.hide().append(metadata_input).appendTo('body'); 73 | form.submit(); 74 | } 75 | 76 | function disableFormElements(form) { 77 | form.find('input[data-disable-with]').each(function() { 78 | var input = $(this); 79 | input.data('ujs:enable-with', input.val()) 80 | .val(input.attr('data-disable-with')) 81 | .attr('disabled', 'disabled'); 82 | }); 83 | } 84 | 85 | function enableFormElements(form) { 86 | form.find('input[data-disable-with]').each(function() { 87 | var input = $(this); 88 | input.val(input.data('ujs:enable-with')).removeAttr('disabled'); 89 | }); 90 | } 91 | 92 | function allowAction(element) { 93 | var message = element.attr('data-confirm'); 94 | return !message || (fire(element, 'confirm') && confirm(message)); 95 | } 96 | 97 | $('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) { 98 | var link = $(this); 99 | if (!allowAction(link)) return false; 100 | 101 | if (link.attr('data-remote')) { 102 | handleRemote(link); 103 | return false; 104 | } else if (link.attr('data-method')) { 105 | handleMethod(link); 106 | return false; 107 | } 108 | }); 109 | 110 | $('form').live('submit.rails', function(e) { 111 | var form = $(this); 112 | if (!allowAction(form)) return false; 113 | 114 | if (form.attr('data-remote')) { 115 | handleRemote(form); 116 | return false; 117 | } else { 118 | disableFormElements(form); 119 | } 120 | }); 121 | 122 | $('form input[type=submit], form button[type=submit], form button:not([type])').live('click.rails', function() { 123 | var button = $(this); 124 | if (!allowAction(button)) return false; 125 | // register the pressed submit button 126 | var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null; 127 | button.closest('form').data('ujs:submit-button', data); 128 | }); 129 | 130 | $('form').live('ajax:beforeSend.rails', function(event) { 131 | if (this == event.target) disableFormElements($(this)); 132 | }); 133 | 134 | $('form').live('ajax:complete.rails', function(event) { 135 | if (this == event.target) enableFormElements($(this)); 136 | }); 137 | })( jQuery ); 138 | -------------------------------------------------------------------------------- /integration/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /integration/public/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #4b7399; 3 | font-family: Verdana, Helvetica, Arial; 4 | font-size: 14px; } 5 | 6 | a { 7 | color: blue; } 8 | a img { 9 | border: none; } 10 | 11 | .clear { 12 | clear: both; 13 | height: 0; 14 | overflow: hidden; } 15 | 16 | #container { 17 | width: 75%; 18 | margin: 0 auto; 19 | background: white; 20 | padding: 20px 40px; 21 | border: solid 1px black; 22 | margin-top: 20px; } 23 | 24 | #flash_notice, 25 | #flash_error, 26 | #flash_alert { 27 | padding: 5px 8px; 28 | margin: 10px 0; } 29 | 30 | #flash_notice { 31 | background-color: #ccffcc; 32 | border: solid 1px #66cc66; } 33 | 34 | #flash_error, 35 | #flash_alert { 36 | background-color: #ffcccc; 37 | border: solid 1px #cc6666; } 38 | 39 | .fieldWithErrors { 40 | display: inline; } 41 | 42 | .error_messages { 43 | width: 400px; 44 | border: 2px solid #cf0000; 45 | padding: 0; 46 | padding-bottom: 12px; 47 | margin-bottom: 20px; 48 | background-color: #f0f0f0; 49 | font-size: 12px; } 50 | .error_messages h2 { 51 | text-align: left; 52 | padding: 5px 5px 5px 15px; 53 | margin: 0; 54 | font-weight: bold; 55 | font-size: 12px; 56 | background-color: #cc0000; 57 | color: white; } 58 | .error_messages p { 59 | margin: 8px 10px; } 60 | .error_messages ul { 61 | margin: 0; } 62 | -------------------------------------------------------------------------------- /integration/public/stylesheets/sass/application.sass: -------------------------------------------------------------------------------- 1 | $primary_color: #4b7399 2 | 3 | body 4 | background-color: $primary_color 5 | font: 6 | family: Verdana, Helvetica, Arial 7 | size: 14px 8 | 9 | a 10 | color: blue 11 | img 12 | border: none 13 | 14 | .clear 15 | clear: both 16 | height: 0 17 | overflow: hidden 18 | 19 | #container 20 | width: 75% 21 | margin: 0 auto 22 | background: white 23 | padding: 20px 40px 24 | border: solid 1px black 25 | margin-top: 20px 26 | 27 | #flash_notice, 28 | #flash_error, 29 | #flash_alert 30 | padding: 5px 8px 31 | margin: 10px 0 32 | 33 | #flash_notice 34 | background-color: #ccffcc 35 | border: solid 1px #66cc66 36 | 37 | #flash_error, 38 | #flash_alert 39 | background-color: #ffcccc 40 | border: solid 1px #cc6666 41 | 42 | .fieldWithErrors 43 | display: inline 44 | 45 | .error_messages 46 | width: 400px 47 | border: 2px solid #cf0000 48 | padding: 0 49 | padding-bottom: 12px 50 | margin-bottom: 20px 51 | background-color: #f0f0f0 52 | font: 53 | size: 12px 54 | h2 55 | text-align: left 56 | padding: 5px 5px 5px 15px 57 | margin: 0 58 | font: 59 | weight: bold 60 | size: 12px 61 | background-color: #cc0000 62 | color: white 63 | p 64 | margin: 8px 10px 65 | ul 66 | margin: 0 67 | -------------------------------------------------------------------------------- /integration/script/cucumber: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 4 | if vendored_cucumber_bin 5 | load File.expand_path(vendored_cucumber_bin) 6 | else 7 | require 'rubygems' unless ENV['NO_RUBYGEMS'] 8 | require 'cucumber' 9 | load Cucumber::BINARY 10 | end 11 | -------------------------------------------------------------------------------- /integration/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /integration/spec/acceptance/acceptance_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") 2 | require "steak" 3 | require 'steak/autocomplete' 4 | 5 | if defined?(ActiveRecord::Base) 6 | begin 7 | require 'database_cleaner' 8 | DatabaseCleaner.strategy = :truncation 9 | rescue LoadError => ignore_if_database_cleaner_not_present 10 | end 11 | end 12 | 13 | # Put your acceptance spec helpers inside /spec/acceptance/support 14 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 15 | 16 | -------------------------------------------------------------------------------- /integration/spec/acceptance/autocomplete_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/acceptance_helper') 2 | 3 | feature "Autocomplete", %q{ 4 | In order to do funky stuff 5 | As a User 6 | I want autocomplete! 7 | } do 8 | 9 | background do 10 | enable_javascript 11 | lambda do 12 | Brand.create! [ 13 | { :name => "Alpha" , :state => true }, 14 | { :name => "Beta" , :state => false }, 15 | { :name => "Gamma" , :state => false }, 16 | { :name => "Kappa" , :state => true }, 17 | { :name => "Kappler" , :state => false } 18 | ] 19 | end.should change(Brand, :count).by(5) 20 | lambda { Feature.create! [{:name => "Shiny"}, {:name => "Glowy"}] }.should change(Feature, :count).by(2) 21 | end 22 | 23 | context "Autocomplete" do 24 | scenario "Autocomplete" do 25 | visit home_page 26 | fill_in "Brand name", :with => "al" 27 | choose_autocomplete_result "Alpha" 28 | find_field("Brand name").value.should include("Alpha") 29 | end 30 | 31 | scenario "Autocomplete, id_element option" do 32 | visit new_id_element_page 33 | fill_in "Brand name", :with => "al" 34 | choose_autocomplete_result "Alpha" 35 | find_field("Brand name").value.should include("Alpha") 36 | find_field("Brand").value.should include(Brand.find_by_name("Alpha").id.to_s) 37 | end 38 | 39 | scenario "Autocomplete for a sub class" do 40 | lambda { ForeignBrand.create! :name => "Omega" }.should change(ForeignBrand, :count) 41 | 42 | visit new_sub_class_page 43 | fill_in "Brand name", :with => "om" 44 | choose_autocomplete_result "Omega" 45 | find_field("Brand name").value.should include("Omega") 46 | end 47 | 48 | scenario "Multiple autocomplete" do 49 | visit new_multiple_section_page 50 | send_to("Brand name", "al") 51 | choose_autocomplete_result "Alpha" 52 | send_to("Brand name", "bet") 53 | choose_autocomplete_result "Beta" 54 | find_field("Brand name").value.should include("Alpha,Beta") 55 | end 56 | 57 | scenario "Autocomplete for Nested Models" do 58 | visit new_nested_model_page 59 | send_to("Feature Name", "sh") 60 | choose_autocomplete_result "Shiny" 61 | find_field("Feature Name").value.should include("Glowy,Shiny") 62 | end 63 | 64 | scenario "Autocomplete for simple_form" do 65 | visit new_simple_form_page 66 | fill_in("Brand name", :with => "al") 67 | choose_autocomplete_result "Alpha" 68 | find_field("Brand name").value.should include("Alpha") 69 | end 70 | 71 | scenario "Autocomplete with scope" do 72 | kappa_brand = Brand.find_by_name('Kappa') 73 | kappa_brand.address = Address.create! 74 | kappa_brand.save! 75 | visit new_scoped_cutocomplete_page 76 | fill_in("Brand name", :with => "ka") 77 | choose_autocomplete_result "Kappa" 78 | find_field("Brand name").value.should include("Kappa") 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /integration/spec/acceptance/support/helpers.rb: -------------------------------------------------------------------------------- 1 | module HelperMethods 2 | def enable_javascript 3 | Capybara.current_driver = :selenium 4 | Capybara.default_wait_time = 1 5 | end 6 | 7 | def send_to(element, key) 8 | find_field(element).native.send_keys(key) 9 | end 10 | end 11 | 12 | RSpec.configuration.include HelperMethods, :type => :acceptance 13 | -------------------------------------------------------------------------------- /integration/spec/acceptance/support/paths.rb: -------------------------------------------------------------------------------- 1 | module NavigationHelpers 2 | # Put helper methods related to the paths in your application here. 3 | 4 | def home_page 5 | "/" 6 | end 7 | 8 | def new_id_element_page 9 | "/id_elements/new" 10 | end 11 | 12 | def new_sub_class_page 13 | "/sub_classes/new" 14 | end 15 | 16 | def new_multiple_section_page 17 | "/multiple_selections/new" 18 | end 19 | 20 | def new_nested_model_page 21 | "/nested_models/new" 22 | end 23 | 24 | def new_simple_form_page 25 | "/simple_forms/new" 26 | end 27 | 28 | def new_scoped_cutocomplete_page 29 | "/scoped_autocompletes/new" 30 | end 31 | end 32 | 33 | RSpec.configuration.include NavigationHelpers, :type => :acceptance 34 | -------------------------------------------------------------------------------- /integration/spec/controllers/formtastics_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe FormtasticsController do 4 | 5 | describe "GET 'new'" do 6 | it "should be successful" do 7 | get 'new' 8 | response.should be_success 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /integration/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require File.expand_path("../../config/environment", __FILE__) 4 | require 'rspec/rails' 5 | require 'capybara' 6 | require 'capybara/rails' 7 | require "capybara/dsl" 8 | 9 | # Requires supporting ruby files with custom matchers and macros, etc, 10 | # in spec/support/ and its subdirectories. 11 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 12 | 13 | RSpec.configure do |config| 14 | # == Mock Framework 15 | # 16 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 17 | # 18 | # config.mock_with :mocha 19 | # config.mock_with :flexmock 20 | # config.mock_with :rr 21 | config.mock_with :rspec 22 | 23 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 24 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 25 | 26 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 27 | # examples within a transaction, remove the following line or assign false 28 | # instead of true. 29 | config.use_transactional_fixtures = false 30 | 31 | config.include Capybara 32 | config.after(:each) do 33 | Capybara.reset_sessions! 34 | Capybara.use_default_driver 35 | DatabaseCleaner.clean 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /integration/spec/views/formtastics/new.html.haml_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "formtastics/new.html.haml" do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /lib/assets/javascripts/autocomplete-rails-uncompressed.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Unobtrusive autocomplete 3 | * 4 | * To use it, you just have to include the HTML attribute autocomplete 5 | * with the autocomplete URL as the value 6 | * 7 | * Example: 8 | * 9 | * 10 | * Optionally, you can use a jQuery selector to specify a field that can 11 | * be updated with the element id whenever you find a matching value 12 | * 13 | * Example: 14 | * 15 | */ 16 | 17 | (function(jQuery) 18 | { 19 | var self = null; 20 | jQuery.fn.railsAutocomplete = function(selector) { 21 | var handler = function() { 22 | if (!this.railsAutoCompleter) { 23 | this.railsAutoCompleter = new jQuery.railsAutocomplete(this); 24 | } 25 | }; 26 | if (jQuery.fn.on !== undefined) { 27 | if (!selector) { 28 | return; 29 | } 30 | return jQuery(document).on('focus',selector,handler); 31 | } 32 | else { 33 | return this.live('focus',handler); 34 | } 35 | }; 36 | 37 | jQuery.railsAutocomplete = function (e) { 38 | var _e = e; 39 | this.init(_e); 40 | }; 41 | jQuery.railsAutocomplete.options = { 42 | showNoMatches: true, 43 | noMatchesLabel: 'no existing match' 44 | } 45 | 46 | jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = { 47 | railsAutocomplete: '0.0.1' 48 | }; 49 | 50 | jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend; 51 | jQuery.railsAutocomplete.fn.extend({ 52 | init: function(e) { 53 | e.delimiter = jQuery(e).attr('data-delimiter') || null; 54 | e.min_length = jQuery(e).attr('data-min-length') || jQuery(e).attr('min-length') || 2; 55 | e.append_to = jQuery(e).attr('data-append-to') || null; 56 | e.autoFocus = jQuery(e).attr('data-auto-focus') || false; 57 | function split( val ) { 58 | return val.split( e.delimiter ); 59 | } 60 | function extractLast( term ) { 61 | return split( term ).pop().replace(/^\s+/,""); 62 | } 63 | 64 | jQuery(e).autocomplete({ 65 | appendTo: e.append_to, 66 | autoFocus: e.autoFocus, 67 | delay: jQuery(e).attr('delay') || 0, 68 | source: function( request, response ) { 69 | var firedFrom = this.element[0]; 70 | var params = {term: extractLast( request.term )}; 71 | if (jQuery(e).attr('data-autocomplete-fields')) { 72 | jQuery.each(jQuery.parseJSON(jQuery(e).attr('data-autocomplete-fields')), function(field, selector) { 73 | params[field] = jQuery(selector).val(); 74 | }); 75 | } 76 | jQuery.getJSON( jQuery(e).attr('data-autocomplete'), params, function() { 77 | var options = {}; 78 | jQuery.extend(options, jQuery.railsAutocomplete.options); 79 | jQuery.each(options, function(key, value) { 80 | if(options.hasOwnProperty(key)) { 81 | var attrVal = jQuery(e).attr('data-' + key); 82 | options[key] = attrVal ? attrVal : value; 83 | } 84 | }); 85 | if(arguments[0].length == 0 && jQuery.inArray(options.showNoMatches, [true, 'true']) >= 0) { 86 | arguments[0] = []; 87 | arguments[0][0] = { id: "", label: options.noMatchesLabel }; 88 | } 89 | jQuery(arguments[0]).each(function(i, el) { 90 | var obj = {}; 91 | obj[el.id] = el; 92 | jQuery(e).data(obj); 93 | }); 94 | response.apply(null, arguments); 95 | jQuery(firedFrom).trigger('railsAutocomplete.source', arguments); 96 | }); 97 | }, 98 | change: function( event, ui ) { 99 | if(!jQuery(this).is('[data-id-element]') || 100 | jQuery(jQuery(this).attr('data-id-element')).val() === "") { 101 | return; 102 | } 103 | jQuery(jQuery(this).attr('data-id-element')).val(ui.item ? ui.item.id : "").trigger('change'); 104 | 105 | if (jQuery(this).attr('data-update-elements')) { 106 | var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements")); 107 | var data = ui.item ? jQuery(this).data(ui.item.id.toString()) : {}; 108 | if(update_elements && jQuery(update_elements['id']).val() === "") { 109 | return; 110 | } 111 | for (var key in update_elements) { 112 | var element = jQuery(update_elements[key]); 113 | if (element.is(':checkbox')) { 114 | if (data[key] != null) { 115 | element.prop('checked', data[key]); 116 | } 117 | } else { 118 | element.val(ui.item ? data[key] : "").trigger('change'); 119 | } 120 | } 121 | } 122 | }, 123 | search: function() { 124 | // custom minLength 125 | var term = extractLast( this.value ); 126 | if ( term.length < e.min_length ) { 127 | return false; 128 | } 129 | }, 130 | focus: function() { 131 | // prevent value inserted on focus 132 | return false; 133 | }, 134 | select: function( event, ui ) { 135 | // first ensure value is a string 136 | ui.item.value = ui.item.value.toString(); 137 | if(ui.item.value.toLowerCase().indexOf('no match') != -1 || ui.item.value.toLowerCase().indexOf('too many results') != -1){ 138 | jQuery(this).trigger('railsAutocomplete.noMatch', ui); 139 | return false; 140 | } 141 | var terms = split( this.value ); 142 | // remove the current input 143 | terms.pop(); 144 | // add the selected item 145 | terms.push( ui.item.value ); 146 | // add placeholder to get the comma-and-space at the end 147 | if (e.delimiter != null) { 148 | terms.push( "" ); 149 | this.value = terms.join( e.delimiter ); 150 | } else { 151 | this.value = terms.join(""); 152 | if (jQuery(this).attr('data-id-element')) { 153 | jQuery(jQuery(this).attr('data-id-element')).val(ui.item.id).trigger('change'); 154 | } 155 | if (jQuery(this).attr('data-update-elements')) { 156 | var data = ui.item; 157 | var new_record = ui.item.value.indexOf('Create New') != -1 ? true : false; 158 | var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements")); 159 | for (var key in update_elements) { 160 | if(jQuery(update_elements[key]).attr("type") === "checkbox"){ 161 | if(data[key] === true || data[key] === 1) { 162 | jQuery(update_elements[key]).attr("checked","checked"); 163 | } 164 | else { 165 | jQuery(update_elements[key]).removeAttr("checked"); 166 | } 167 | } 168 | else{ 169 | if((new_record && data[key] && data[key].indexOf('Create New') == -1) || !new_record){ 170 | jQuery(update_elements[key]).val(data[key]).trigger('change'); 171 | }else{ 172 | jQuery(update_elements[key]).val('').trigger('change'); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | var remember_string = this.value; 179 | jQuery(this).bind('keyup.clearId', function(){ 180 | if(jQuery.trim(jQuery(this).val()) != jQuery.trim(remember_string)){ 181 | jQuery(jQuery(this).attr('data-id-element')).val("").trigger('change'); 182 | jQuery(this).unbind('keyup.clearId'); 183 | } 184 | }); 185 | jQuery(e).trigger('railsAutocomplete.select', ui); 186 | 187 | return false; 188 | } 189 | }); 190 | jQuery(e).trigger('railsAutocomplete.init'); 191 | } 192 | }); 193 | 194 | jQuery(document).ready(function(){ 195 | jQuery('input[data-autocomplete]').railsAutocomplete('input[data-autocomplete]'); 196 | }); 197 | })(jQuery); 198 | -------------------------------------------------------------------------------- /lib/assets/javascripts/autocomplete-rails.js: -------------------------------------------------------------------------------- 1 | !function(t){t.fn.railsAutocomplete=function(e){var a=function(){this.railsAutoCompleter||(this.railsAutoCompleter=new t.railsAutocomplete(this))};if(void 0!==t.fn.on){if(!e)return;return t(document).on("focus",e,a)}return this.live("focus",a)},t.railsAutocomplete=function(t){var e=t;this.init(e)},t.railsAutocomplete.options={showNoMatches:!0,noMatchesLabel:"no existing match"},t.railsAutocomplete.fn=t.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},t.railsAutocomplete.fn.extend=t.railsAutocomplete.extend=t.extend,t.railsAutocomplete.fn.extend({init:function(e){function a(t){return t.split(e.delimiter)}function i(t){return a(t).pop().replace(/^\s+/,"")}e.delimiter=t(e).attr("data-delimiter")||null,e.min_length=t(e).attr("data-min-length")||t(e).attr("min-length")||2,e.append_to=t(e).attr("data-append-to")||null,e.autoFocus=t(e).attr("data-auto-focus")||!1,t(e).autocomplete({appendTo:e.append_to,autoFocus:e.autoFocus,delay:t(e).attr("delay")||0,source:function(a,r){var n=this.element[0],o={term:i(a.term)};t(e).attr("data-autocomplete-fields")&&t.each(t.parseJSON(t(e).attr("data-autocomplete-fields")),function(e,a){o[e]=t(a).val()}),t.getJSON(t(e).attr("data-autocomplete"),o,function(){var a={};t.extend(a,t.railsAutocomplete.options),t.each(a,function(i,r){if(a.hasOwnProperty(i)){var n=t(e).attr("data-"+i);a[i]=n?n:r}}),0==arguments[0].length&&t.inArray(a.showNoMatches,[!0,"true"])>=0&&(arguments[0]=[],arguments[0][0]={id:"",label:a.noMatchesLabel}),t(arguments[0]).each(function(a,i){var r={};r[i.id]=i,t(e).data(r)}),r.apply(null,arguments),t(n).trigger("railsAutocomplete.source",arguments)})},change:function(e,a){if(t(this).is("[data-id-element]")&&""!==t(t(this).attr("data-id-element")).val()&&(t(t(this).attr("data-id-element")).val(a.item?a.item.id:"").trigger("change"),t(this).attr("data-update-elements"))){var i=t.parseJSON(t(this).attr("data-update-elements")),r=a.item?t(this).data(a.item.id.toString()):{};if(i&&""===t(i.id).val())return;for(var n in i){var o=t(i[n]);o.is(":checkbox")?null!=r[n]&&o.prop("checked",r[n]):o.val(a.item?r[n]:"").trigger("change")}}},search:function(){var t=i(this.value);return t.length :collection 24 | # end 25 | # 26 | # Now, on your view, all you have to do is have a text field like: 27 | # 28 | # f.text_field :brand_name, :autocomplete => autocomplete_brand_name_products_path 29 | # 30 | # 31 | # Yajl is used by default to encode results, if you want to use a different encoder 32 | # you can specify your custom encoder via block 33 | # 34 | # class ProductsController < Admin::BaseController 35 | # autocomplete :brand, :name do |items| 36 | # CustomJSONEncoder.encode(items) 37 | # end 38 | # end 39 | # 40 | module ClassMethods 41 | def autocomplete(object, method, options = {}, &block) 42 | 43 | define_method("get_prefix") do |model| 44 | if defined?(Mongoid::Document) && model.include?(Mongoid::Document) 45 | 'mongoid' 46 | elsif defined?(MongoMapper::Document) && model.include?(MongoMapper::Document) 47 | 'mongo_mapper' 48 | else 49 | 'active_record' 50 | end 51 | end 52 | define_method("get_autocomplete_order") do |method, options, model=nil| 53 | method("#{get_prefix(get_object(options[:class_name] || object))}_get_autocomplete_order").call(method, options, model) 54 | end 55 | 56 | define_method("get_autocomplete_items") do |parameters| 57 | method("#{get_prefix(get_object(options[:class_name] || object))}_get_autocomplete_items").call(parameters) 58 | end 59 | 60 | define_method("autocomplete_#{object}_#{method}") do 61 | 62 | method = options[:column_name] if options.has_key?(:column_name) 63 | 64 | term = params[:term] 65 | 66 | if term && !term.blank? 67 | #allow specifying fully qualified class name for model object 68 | class_name = options[:class_name] || object 69 | items = get_autocomplete_items(:model => get_object(class_name), \ 70 | :options => options, :term => term, :method => method) 71 | else 72 | items = {} 73 | end 74 | 75 | render :json => json_for_autocomplete(items, options[:display_value] ||= method, options[:extra_data], &block), root: false 76 | end 77 | end 78 | end 79 | 80 | # Returns a limit that will be used on the query 81 | def get_autocomplete_limit(options) 82 | options[:limit] ||= 10 83 | end 84 | 85 | # Returns parameter model_sym as a constant 86 | # 87 | # get_object(:actor) 88 | # # returns a Actor constant supposing it is already defined 89 | # 90 | def get_object(model_sym) 91 | object = model_sym.to_s.camelize.constantize 92 | end 93 | 94 | # 95 | # Returns a hash with three keys actually used by the Autocomplete jQuery-ui 96 | # Can be overriden to show whatever you like 97 | # Hash also includes a key/value pair for each method in extra_data 98 | # 99 | def json_for_autocomplete(items, method, extra_data=[]) 100 | items = items.collect do |item| 101 | hash = HashWithIndifferentAccess.new({"id" => item.id.to_s, "label" => item.send(method), "value" => item.send(method)}) 102 | extra_data.each do |datum| 103 | hash[datum] = item.send(datum) 104 | end if extra_data 105 | # TODO: Come back to remove this if clause when test suite is better 106 | hash 107 | end 108 | if block_given? 109 | yield(items) 110 | else 111 | items 112 | end 113 | end 114 | end 115 | end 116 | 117 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/form_helper.rb: -------------------------------------------------------------------------------- 1 | module ActionView 2 | module Helpers 3 | module FormHelper 4 | # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) and 5 | # that is populated with jQuery's autocomplete plugin. 6 | # 7 | # ==== Examples 8 | # autocomplete_field(:post, :title, author_autocomplete_path, :size => 20) 9 | # # => 10 | # 11 | def autocomplete_field(object_name, method, source, options ={}) 12 | options["data-autocomplete"] = source 13 | text_field(object_name, method, rewrite_autocomplete_option(options)) 14 | end 15 | end 16 | 17 | module FormTagHelper 18 | # Creates a standard text field that can be populated with jQuery's autocomplete plugin 19 | # 20 | # ==== Examples 21 | # autocomplete_field_tag 'address', '', address_autocomplete_path, :size => 75 22 | # # => 23 | # 24 | def autocomplete_field_tag(name, value, source, options ={}) 25 | options["data-autocomplete"] = source 26 | text_field_tag(name, value, rewrite_autocomplete_option(options)) 27 | end 28 | end 29 | 30 | # 31 | # Method used to rename the autocomplete key to a more standard 32 | # data-autocomplete key 33 | # 34 | private 35 | def rewrite_autocomplete_option(options) 36 | options["data-autocomplete-fields"] = JSON.generate(options.delete :fields) if options[:fields] 37 | options["data-update-elements"] = JSON.generate(options.delete :update_elements) if options[:update_elements] 38 | options["data-id-element"] = options.delete :id_element if options[:id_element] 39 | options["data-append-to"] = options.delete :append_to if options[:append_to] 40 | options 41 | end 42 | end 43 | end 44 | 45 | class ActionView::Helpers::FormBuilder #:nodoc: 46 | def autocomplete_field(method, source, options = {}) 47 | @template.autocomplete_field(@object_name, method, source, objectify_options(options)) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/formtastic.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Load the formtastic plugin if using Formtastic 3 | # 4 | begin 5 | require 'formtastic' 6 | begin 7 | require "formtastic/version" 8 | rescue LoadError 9 | end 10 | 11 | if defined?(Formtastic::VERSION) 12 | # 13 | # Formtastic 2.x 14 | # 15 | 16 | module Formtastic 17 | module Inputs 18 | class AutocompleteInput 19 | include Base 20 | include Base::Stringish 21 | 22 | def to_html 23 | input_wrapping do 24 | label_html << 25 | builder.autocomplete_field(method, options.delete(:url), input_html_options) 26 | end 27 | end 28 | end 29 | end 30 | end 31 | else 32 | 33 | # 34 | # Formtastic 1.x 35 | # 36 | class Formtastic::SemanticFormBuilder < ActionView::Helpers::FormBuilder 37 | include RailsJQueryAutocomplete::FormtasticPlugin 38 | end 39 | end 40 | rescue LoadError 41 | end 42 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/formtastic_plugin.rb: -------------------------------------------------------------------------------- 1 | module RailsJQueryAutocomplete 2 | module FormtasticPlugin 3 | def autocomplete_input(method, options = {}) 4 | if options.key?(:selected) || options.key?(:checked) || options.key?(:default) 5 | ::ActiveSupport::Deprecation.warn( 6 | "The :selected, :checked (and :default) options are deprecated in Formtastic and will be removed from 1.0. " << 7 | "Please set default values in your models (using an after_initialize callback) or in your controller set-up. " << 8 | "See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information.", caller) 9 | end 10 | 11 | options[:required] = method_required?(method) unless options.key?(:required) 12 | options[:as] ||= "autocompleted_string" 13 | 14 | html_class = [ options[:as], (options[:required] ? :required : :optional) ] 15 | html_class << 'error' if @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank? 16 | 17 | wrapper_html = options.delete(:wrapper_html) || {} 18 | wrapper_html[:id] ||= generate_html_id(method) 19 | wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ') 20 | 21 | if options[:input_html] && options[:input_html][:id] 22 | options[:label_html] ||= {} 23 | options[:label_html][:for] ||= options[:input_html][:id] 24 | end 25 | 26 | input_parts = self.class.inline_order.dup 27 | input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden 28 | 29 | list_item_content = input_parts.map do |type| 30 | send(:"inline_#{type}_for", method, options) 31 | end.compact.join("\n") 32 | 33 | return template.content_tag(:li, Formtastic::Util.html_safe(list_item_content), wrapper_html) 34 | end 35 | 36 | alias_method :autocompleted_input, :autocomplete_input 37 | 38 | 39 | protected 40 | def autocompleted_string_input(method, options) 41 | self.label(method, options_for_label(options)) << autocomplete_field(method, options.delete(:url), options) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/orm.rb: -------------------------------------------------------------------------------- 1 | module RailsJQueryAutocomplete 2 | module Orm 3 | autoload :ActiveRecord , 'rails-jquery-autocomplete/orm/active_record' 4 | autoload :Mongoid , 'rails-jquery-autocomplete/orm/mongoid' 5 | autoload :MongoMapper , 'rails-jquery-autocomplete/orm/mongo_mapper' 6 | end 7 | end 8 | 9 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/orm/active_record.rb: -------------------------------------------------------------------------------- 1 | module RailsJQueryAutocomplete 2 | module Orm 3 | module ActiveRecord 4 | def active_record_get_autocomplete_order(method, options, model=nil) 5 | order = options[:order] 6 | 7 | table_prefix = model ? "#{model.table_name}." : "" 8 | sql = if sqlite? 9 | order || "LOWER(#{method}) ASC" 10 | else 11 | order || "LOWER(#{table_prefix}#{method}) ASC" 12 | end 13 | 14 | Arel.sql(sql) 15 | end 16 | 17 | def active_record_get_autocomplete_items(parameters) 18 | model = parameters[:model] 19 | term = parameters[:term] 20 | options = parameters[:options] 21 | method = options[:hstore] ? options[:hstore][:method] : parameters[:method] 22 | scopes = Array(options[:scopes]) 23 | where = options[:where] 24 | limit = get_autocomplete_limit(options) 25 | order = active_record_get_autocomplete_order(method, options, model) 26 | 27 | items = (::Rails::VERSION::MAJOR * 10 + ::Rails::VERSION::MINOR) >= 40 ? model.where(nil) : model.scoped 28 | 29 | scopes.each { |scope| items = items.send(scope) } unless scopes.empty? 30 | 31 | items = items.select(get_autocomplete_select_clause(model, method, options)) unless options[:full_model] 32 | items = items.where(get_autocomplete_where_clause(model, term, method, options)). 33 | limit(limit).order(order) 34 | items = items.where(where) unless where.blank? 35 | 36 | items 37 | end 38 | 39 | def get_autocomplete_select_clause(model, method, options) 40 | if sqlite? 41 | table_name = model.quoted_table_name 42 | ([ 43 | "#{table_name}.#{model.connection.quote_column_name(model.primary_key)} as #{model.primary_key}", 44 | "#{table_name}.#{model.connection.quote_column_name(method)} as #{method}" 45 | ] + (options[:extra_data].blank? ? [] : options[:extra_data])) 46 | else 47 | table_name = model.table_name 48 | (["#{table_name}.#{model.primary_key}", "#{table_name}.#{method}"] + (options[:extra_data].blank? ? [] : options[:extra_data])) 49 | end 50 | end 51 | 52 | def get_autocomplete_where_clause(model, term, method, options) 53 | table_name = model.table_name 54 | is_full_search = options[:full] 55 | is_case_sensitive_search = options[:case_sensitive] 56 | like_clause = (postgres?(model) && !is_case_sensitive_search ? 'ILIKE' : 'LIKE') 57 | column_transform = is_case_sensitive_search ? '' : 'LOWER' 58 | term = "#{(is_full_search ? '%' : '')}#{term.gsub(/([_%\\])/, '\\\\\1')}%" 59 | if options[:hstore] 60 | ["#{column_transform}(#{table_name}.#{method} -> '#{options[:hstore][:key]}') LIKE #{column_transform}(?)", term] 61 | elsif sqlite? 62 | ["#{column_transform}(#{method}) #{like_clause} #{column_transform}(?)", term] 63 | else 64 | ["#{column_transform}(#{table_name}.#{method}) #{like_clause} #{column_transform}(?)", term] 65 | end 66 | end 67 | 68 | protected 69 | 70 | def sqlite? 71 | begin 72 | return ::ActiveRecord::Base.connection.to_s.match(/SQLite/) 73 | rescue ::ActiveRecord::ConnectionNotEstablished 74 | return false 75 | end 76 | return false 77 | end 78 | 79 | def postgres?(model) 80 | # Figure out if this particular model uses the PostgreSQL adapter 81 | model.connection.class.to_s.match(/PostgreSQLAdapter/) 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/orm/mongo_mapper.rb: -------------------------------------------------------------------------------- 1 | module RailsJQueryAutocomplete 2 | module Orm 3 | module MongoMapper 4 | def mongo_mapper_get_autocomplete_order(method, options, model=nil) 5 | order = options[:order] 6 | if order 7 | order.split(',').collect do |fields| 8 | sfields = fields.split 9 | [sfields[0].downcase.to_sym, sfields[1].downcase.to_sym] 10 | end 11 | else 12 | [[method.to_sym, :asc]] 13 | end 14 | end 15 | 16 | def mongo_mapper_get_autocomplete_items(parameters) 17 | model = parameters[:model] 18 | method = parameters[:method] 19 | options = parameters[:options] 20 | is_full_search = options[:full] 21 | is_case_sensitive_search = options[:case_sensitive] 22 | term = parameters[:term] 23 | limit = get_autocomplete_limit(options) 24 | order = mongo_mapper_get_autocomplete_order(method, options) 25 | 26 | search = (is_full_search ? '.*' : '^') + term + '.*' 27 | search = Regexp.new(search, !is_case_sensitive_search) 28 | items = model.where(method.to_sym => search).limit(limit).sort(order) 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/orm/mongoid.rb: -------------------------------------------------------------------------------- 1 | module RailsJQueryAutocomplete 2 | module Orm 3 | module Mongoid 4 | def mongoid_get_autocomplete_order(method, options, model=nil) 5 | order = options[:order] 6 | if order 7 | order.split(',').collect do |fields| 8 | sfields = fields.split 9 | [sfields[0].downcase.to_sym, sfields[1].downcase.to_sym] 10 | end 11 | else 12 | [[method.to_sym, :asc]] 13 | end 14 | end 15 | 16 | def mongoid_get_autocomplete_items(parameters) 17 | model = parameters[:model] 18 | method = parameters[:method] 19 | options = parameters[:options] 20 | is_full_search = options[:full] 21 | is_case_sensitive_search = options[:case_sensitive] 22 | term = parameters[:term] 23 | limit = get_autocomplete_limit(options) 24 | order = mongoid_get_autocomplete_order(method, options) 25 | 26 | if is_full_search 27 | search = '.*' + Regexp.escape(term) + '.*' 28 | else 29 | search = '^' + Regexp.escape(term) 30 | end 31 | search = Regexp.new(search, !is_case_sensitive_search) 32 | items = model.where(method.to_sym => search).limit(limit).order_by(order) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/rails/engine.rb: -------------------------------------------------------------------------------- 1 | module RailsJQueryAutocomplete 2 | module Rails 3 | class Engine < ::Rails::Engine ; end 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/simple_form_plugin.rb: -------------------------------------------------------------------------------- 1 | module SimpleForm 2 | module Inputs 3 | module Autocomplete 4 | # 5 | # Method used to rename the autocomplete key to a more standard 6 | # data-autocomplete key 7 | # 8 | def rewrite_autocomplete_option 9 | new_options = {} 10 | new_options["data-autocomplete-fields"] = JSON.generate(options.delete :fields) if options[:fields] 11 | new_options["data-update-elements"] = JSON.generate(options.delete :update_elements) if options[:update_elements] 12 | new_options["data-id-element"] = options.delete :id_element if options[:id_element] 13 | input_html_options.merge new_options 14 | end 15 | end 16 | 17 | class AutocompleteInput < Base 18 | include Autocomplete 19 | 20 | protected 21 | def limit 22 | column && column.limit 23 | end 24 | 25 | def has_placeholder? 26 | placeholder_present? 27 | end 28 | 29 | def input(args = nil) 30 | # This branching is to deal with a change beginning in simple_form 3.0.2 and above to ensure backwards compatibility 31 | if args.nil? 32 | @builder.autocomplete_field( 33 | attribute_name, 34 | options[:url], 35 | rewrite_autocomplete_option 36 | ) 37 | else 38 | @builder.autocomplete_field( 39 | attribute_name, 40 | options[:url], 41 | merge_wrapper_options(rewrite_autocomplete_option, args) 42 | ) 43 | end 44 | end 45 | end 46 | 47 | class AutocompleteCollectionInput < CollectionInput 48 | include Autocomplete 49 | 50 | def input(opts) 51 | # http://www.codeofficer.com/blog/entry/form_builders_in_rails_discovering_field_names_and_ids_for_javascript/ 52 | hidden_id = "#{object_name}_#{attribute_name}_hidden".gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") 53 | id_element = options[:id_element] 54 | if id_element 55 | id_element << ", #" << hidden_id 56 | else 57 | id_element = "#" + hidden_id 58 | end 59 | options[:id_element] = id_element 60 | 61 | # This branching is to deal with a change beginning in simple_form 3.0.2 and above to ensure backwards compatibility 62 | if opts.nil? 63 | autocomplete_options = rewrite_autocomplete_option 64 | else 65 | merge_wrapper_options(rewrite_autocomplete_option, args) 66 | end 67 | 68 | label_method, value_method = detect_collection_methods 69 | association = object.send(reflection.name) 70 | if association && association.respond_to?(label_method) 71 | autocomplete_options[:value] = association.send(label_method) 72 | end 73 | out = @builder.autocomplete_field( 74 | attribute_name, 75 | options[:url], 76 | autocomplete_options 77 | ) 78 | hidden_options = if association && association.respond_to?(value_method) 79 | new_options = {} 80 | new_options[:value] = association.send(value_method) 81 | input_html_options.merge new_options 82 | else 83 | input_html_options 84 | end 85 | hidden_options[:id] = hidden_id 86 | out << @builder.hidden_field( 87 | attribute_name, 88 | hidden_options 89 | ) 90 | out.html_safe 91 | end 92 | end 93 | end 94 | 95 | class FormBuilder 96 | map_type :autocomplete, :to => SimpleForm::Inputs::AutocompleteInput 97 | map_type :autocomplete_collection, :to => SimpleForm::Inputs::AutocompleteCollectionInput 98 | end 99 | 100 | end 101 | -------------------------------------------------------------------------------- /lib/rails-jquery-autocomplete/version.rb: -------------------------------------------------------------------------------- 1 | module RailsJQueryAutocomplete 2 | VERSION = '1.0.5' 3 | end 4 | -------------------------------------------------------------------------------- /lib/steak/autocomplete.rb: -------------------------------------------------------------------------------- 1 | module Steak 2 | module Autocomplete 3 | def choose_autocomplete_result(text, input_id="input[data-autocomplete]") 4 | page.execute_script %Q{ $('#{input_id}').trigger("focus") } 5 | page.execute_script %Q{ $('#{input_id}').trigger("keydown") } 6 | sleep 1 7 | page.execute_script %Q{ $('.ui-menu-item a:contains("#{text}")').trigger("mouseenter").trigger("click"); } 8 | end 9 | end 10 | end 11 | 12 | RSpec.configuration.include Steak::Autocomplete, :type => :acceptance 13 | -------------------------------------------------------------------------------- /rails-jquery-autocomplete.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "rails-jquery-autocomplete/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = %q{rails-jquery-autocomplete} 7 | s.version = RailsJQueryAutocomplete::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["David Padilla", "Joiey Seeley", "Sundus Yousuf"] 10 | s.email = %q{david@padilla.cc joiey.seeley@gmail.com sundusahmedyousuf@gmail.com} 11 | s.homepage = %q{https://github.com/bigtunacan/rails-jquery-autocomplete/} 12 | s.summary = %q{Use jQuery's autocomplete plugin with Rails 4+.} 13 | s.description = %q{Use jQuery's autocomplete plugin with Rails 4+.} 14 | s.license = %q{MIT} 15 | 16 | s.add_dependency('rails', '>= 3.2') 17 | 18 | s.add_development_dependency 'sqlite3-ruby' 19 | s.add_development_dependency 'minitest' 20 | s.add_development_dependency 'mongoid', '>= 2.0.0' 21 | s.add_development_dependency 'mongo_mapper', '>= 0.9' 22 | #s.add_development_dependency 'mongo', '~> 1.6.2' 23 | s.add_development_dependency 'bson_ext', '~> 1.6.2' 24 | s.add_development_dependency 'guard' 25 | s.add_development_dependency 'guard-test' 26 | s.add_development_dependency 'test-unit-rr' 27 | s.add_development_dependency 'shoulda', '~> 3.0.1' 28 | s.add_development_dependency 'uglifier' 29 | s.add_development_dependency 'simple_form', '~>1.5' 30 | s.add_development_dependency 'debugger' if RUBY_VERSION < '2.0.0' 31 | s.add_development_dependency 'byebug' if RUBY_VERSION > '2.0.0' 32 | 33 | s.files = Dir['lib/**/*'] + %w{CHANGELOG.md LICENSE README.md Rakefile} 34 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 35 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 36 | s.require_paths = ["lib"] 37 | end 38 | 39 | -------------------------------------------------------------------------------- /test-unit.yml: -------------------------------------------------------------------------------- 1 | runner: console 2 | console_options: 3 | #arguments: --use-color=true --verbose=verbose 4 | arguments: --use-color=true 5 | color_scheme: new_and_improved 6 | color_schemes: 7 | new_and_improved: 8 | success: 9 | name: green 10 | bold: false 11 | error: 12 | name: magenta 13 | bold: false 14 | failure: 15 | name: red 16 | bold: true 17 | notification: 18 | name: cyan 19 | bold: false 20 | pending: 21 | name: yellow 22 | bold: false 23 | omission: 24 | name: white 25 | bold: true 26 | 27 | -------------------------------------------------------------------------------- /test/form_helper_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class Post 4 | attr_accessor :author 5 | end 6 | 7 | class FormHelperTest < ActionView::TestCase 8 | def test_text_field_tag 9 | assert_match(/autocomplete=\"some\/path\"/, text_field_tag('field_name', '', :autocomplete => 'some/path')) 10 | end 11 | 12 | def test_text_field 13 | post = Post.new 14 | assert_match(/autocomplete=\"some\/path\"/, text_field(:post, :author, :autocomplete => 'some/path')) 15 | end 16 | 17 | def test_autocomplete_field_tag 18 | assert_match(/data-autocomplete=\"some\/path\"/, autocomplete_field_tag('field_name', '', 'some/path')) 19 | end 20 | 21 | def test_autocomplete_field 22 | post= Post.new 23 | assert_match(/data-autocomplete=\"some\/path\"/, autocomplete_field(:post, :author, 'some/path')) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/generators/autocomplete/install_generator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'generators/autocomplete/install_generator' 3 | 4 | module Autocomplete 5 | class InstallGeneratorTest < Test::Unit::TestCase 6 | def setup 7 | @destination = File.join('tmp', 'test_app') 8 | @source = InstallGenerator.source_root 9 | @filename = File.join(@destination, 'public', 'javascripts', 'autocomplete-rails.js') 10 | 11 | File.unlink(@filename) if File.exists?(@filename) 12 | 13 | InstallGenerator.start([], :destination_root => @destination) 14 | end 15 | 16 | def test_install 17 | assert File.exists?(@filename) 18 | 19 | assert_equal( 20 | File.read(File.join(@source, 'autocomplete-rails.js')), 21 | File.read(@filename) 22 | ) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/generators/autocomplete/uncompressed_generator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'generators/autocomplete/uncompressed_generator' 3 | 4 | module Autocomplete 5 | class UncompressedGeneratorTest < Test::Unit::TestCase 6 | def setup 7 | @destination = File.join('tmp', 'test_app') 8 | @source = UncompressedGenerator.source_root 9 | @filename = File.join(@destination, 'public', 'javascripts', 'autocomplete-rails.js') 10 | 11 | File.unlink(@filename) if File.exists?(@filename) 12 | 13 | UncompressedGenerator.start([], :destination_root => @destination) 14 | end 15 | 16 | def test_install 17 | assert File.exists?(@filename) 18 | 19 | assert_equal( 20 | File.read(File.join(@source, 'autocomplete-rails-uncompressed.js')), 21 | File.read(@filename) 22 | ) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/lib/rails-jquery-autocomplete/autocomplete_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module RailsJQueryAutocomplete 4 | class AutocompleteTest < Test::Unit::TestCase 5 | include RailsJQueryAutocomplete::Autocomplete 6 | 7 | context 'ClassMethods' do 8 | context '#autocomplete' do 9 | context '#get_prefix' do 10 | context 'Mongoid and MongoMapper is not defined' do 11 | setup do 12 | ActorsController = Class.new(ActionController::Base) 13 | ActorsController.autocomplete(:movie, :name) 14 | @controller = ActorsController.new 15 | 16 | @model = Class.new(ActiveRecord::Base) 17 | 18 | Object.send(:remove_const, :Mongoid) 19 | Object.send(:remove_const, :MongoMapper) 20 | end 21 | 22 | should 'not raise exception' do 23 | @controller.get_prefix(@model) 24 | end 25 | end 26 | end 27 | end 28 | end 29 | 30 | context '#get_autocomplete_limit' do 31 | context 'the limit option was specified' do 32 | should "return the limit option" do 33 | assert_equal 99, get_autocomplete_limit({:limit => 99}) 34 | end 35 | end 36 | 37 | context 'the limit option is not specified' do 38 | should 'return 10' do 39 | assert_equal 10, get_autocomplete_limit({}) 40 | end 41 | end 42 | end 43 | 44 | context '#get_object' do 45 | should 'return the specified sym as a class name' do 46 | symbol = Object.new 47 | class_object = Class.new 48 | mock(symbol).to_s.mock!.camelize.mock!.constantize { class_object } 49 | assert_equal class_object, get_object(symbol) 50 | end 51 | end 52 | 53 | context '#json_for_autocomplete' do 54 | should 'parse items to JSON' do 55 | item = mock(Object) 56 | mock(item).send(:name).times(2) { 'Object Name' } 57 | mock(item).id { 1 } 58 | items = [item] 59 | response = self.json_for_autocomplete(items, :name).first 60 | assert_equal response["id"], "1" 61 | assert_equal response["value"], "Object Name" 62 | assert_equal response["label"], "Object Name" 63 | end 64 | 65 | should 'return an instance of HashWithIndifferentAccess' do 66 | item = mock(Object) 67 | mock(item).send(:name).times(2) { 'Object Name' } 68 | mock(item).id { 1 } 69 | items = [item] 70 | response = self.json_for_autocomplete(items, :name).first 71 | assert_equal response.is_a?(HashWithIndifferentAccess), true 72 | assert_equal response["id"], "1" 73 | assert_equal response[:id], "1" 74 | end 75 | 76 | context 'with extra data' do 77 | should 'add that extra data to result' do 78 | item = mock(Object) 79 | mock(item).send(:name).times(2) { 'Object Name' } 80 | mock(item).id { 1 } 81 | mock(item).send("extra") { 'Object Extra ' } 82 | 83 | items = [item] 84 | response = self.json_for_autocomplete(items, :name, ["extra"]).first 85 | 86 | assert_equal "1" , response["id"] 87 | assert_equal "Object Name" , response["value"] 88 | assert_equal "Object Name" , response["label"] 89 | end 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /test/lib/rails-jquery-autocomplete/orm/active_record_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module RailsJQueryAutocomplete 4 | module Orm 5 | class ActiveRecordTest < Test::Unit::TestCase 6 | include RailsJQueryAutocomplete::Orm::ActiveRecord 7 | 8 | context "#get_autocomplete_order" do 9 | context 'order is specified' do 10 | should 'returns that order option' do 11 | assert_equal "field ASC", active_record_get_autocomplete_order(:field, {:order => 'field ASC'}) 12 | end 13 | end 14 | 15 | context 'no order is specified' do 16 | should 'return the order clause by the LOWER(table_name.field) ASC' do 17 | assert_equal "LOWER(field) ASC", active_record_get_autocomplete_order(:field, {}) 18 | end 19 | 20 | context 'a different model is specified' do 21 | should 'return the order clause by the LOWER(table_name.field) ASC' do 22 | model = Object.new 23 | mock(model).table_name { 'table_name' } 24 | assert_equal "LOWER(table_name.field) ASC", active_record_get_autocomplete_order(:field, {}, model) 25 | end 26 | end 27 | end 28 | end 29 | 30 | context '#active_record_get_autocomplete_items' do 31 | should 'retrieve the items from ActiveRecord' do 32 | class Dog ; end 33 | 34 | model = Dog 35 | scoped = [] 36 | whered = [] 37 | term = 'query' 38 | method = :field 39 | 40 | options = { 41 | :model => model, 42 | :term => term, 43 | :method => method, 44 | :options => {} 45 | } 46 | 47 | mock(self).get_autocomplete_limit(anything) { 10 } 48 | mock(self).active_record_get_autocomplete_order(anything, anything, anything) { "order ASC" } 49 | mock(self).get_autocomplete_select_clause(model, method, {}) { ["field"] } 50 | mock(self).get_autocomplete_where_clause(model, term, method, {}) { ["WHERE something"] } 51 | mock(model).table_name.times(any_times) { 'model_table_name' } 52 | 53 | mock(model).scoped { model } 54 | mock(model).select(["field"]) { model } 55 | mock(model).where(["WHERE something"]).mock!.limit(10).mock!. 56 | order("order ASC") { 1 } 57 | 58 | assert_equal 1, active_record_get_autocomplete_items(options) 59 | end 60 | 61 | should 'use hstore method if present' do 62 | class Dog ; end 63 | 64 | model = Dog 65 | scoped = [] 66 | whered = [] 67 | term = 'query' 68 | method = :field 69 | hsmethod = :hsfield 70 | 71 | options = { 72 | :model => model, 73 | :term => term, 74 | :method => method, 75 | :options => {hstore: {method: hsmethod}} 76 | } 77 | 78 | mock(self).get_autocomplete_limit(anything) { 10 } 79 | mock(self).active_record_get_autocomplete_order(anything, anything, anything) { "order ASC" } 80 | mock(self).get_autocomplete_select_clause(model, hsmethod, options[:options]) { ["hsfield"] } 81 | mock(self).get_autocomplete_where_clause(model, term, hsmethod, options[:options]) { ["WHERE something"] } 82 | mock(model).table_name.times(any_times) { 'model_table_name' } 83 | 84 | mock(model).scoped { model } 85 | mock(model).select(["hsfield"]) { model } 86 | mock(model).where(["WHERE something"]).mock!.limit(10).mock!. 87 | order("order ASC") { 1 } 88 | 89 | assert_equal 1, active_record_get_autocomplete_items(options) 90 | end 91 | end 92 | 93 | context '#get_autocomplete_select_clause' do 94 | setup do 95 | @model = Object.new 96 | mock(@model).table_name { 'table_name' } 97 | mock(@model).primary_key { 'id' } 98 | end 99 | 100 | should 'create a select clause' do 101 | assert_equal ["table_name.id", "table_name.method"], 102 | get_autocomplete_select_clause(@model, :method, {}) 103 | end 104 | 105 | should 'create a select clause with hstore method' do 106 | assert_equal ["table_name.id", "table_name.hsmethod"], 107 | get_autocomplete_select_clause(@model, :hsmethod, {hstore: {method: :hsmethod}}) 108 | end 109 | 110 | context 'with extra options' do 111 | should 'return those extra fields on the clause' do 112 | options = {:extra_data => ['table_name.created_at']} 113 | 114 | assert_equal ["table_name.id", "table_name.method", "table_name.created_at"], 115 | get_autocomplete_select_clause(@model, :method, options) 116 | end 117 | end 118 | end 119 | 120 | context '#get_autocomplete_where_clause' do 121 | setup do 122 | @model = Object.new 123 | mock(@model).table_name { 'table_name' } 124 | 125 | @term = 'query' 126 | @options = {} 127 | @method = :method 128 | end 129 | 130 | context 'Not Postgres' do 131 | should 'return options for where' do 132 | mock(self).postgres?(@model) { false } 133 | assert_equal ["LOWER(table_name.method) LIKE LOWER(?)", "query%"], get_autocomplete_where_clause(@model, @term, @method, @options) 134 | end 135 | end 136 | 137 | context 'Postgres' do 138 | should 'return options for where with ILIKE' do 139 | mock(self).postgres?(@model) { true } 140 | assert_equal ["LOWER(table_name.method) ILIKE LOWER(?)", "query%"], get_autocomplete_where_clause(@model, @term, @method, @options) 141 | end 142 | end 143 | 144 | context 'HStore' do 145 | should 'return options for where from hstore options' do 146 | mock(self).postgres?(@model) { true } 147 | @options[:hstore] = {method: :hsmethod, key: :hskey} 148 | @method = :hsmethod 149 | assert_equal ["LOWER(table_name.hsmethod -> 'hskey') LIKE LOWER(?)", "query%"], get_autocomplete_where_clause(@model, @term, @method, @options) 150 | end 151 | end 152 | 153 | context 'full search' do 154 | should 'return options for where with the term sourrounded by %%' do 155 | mock(self).postgres?(@model) { false } 156 | @options[:full] = true 157 | assert_equal ["LOWER(table_name.method) LIKE LOWER(?)", "%query%"], get_autocomplete_where_clause(@model, @term, @method, @options) 158 | end 159 | end 160 | end 161 | 162 | context '#postgres?' do 163 | setup do 164 | @model = stub 165 | end 166 | 167 | context 'the connection class is not postgres' do 168 | setup do 169 | mock(@model).connection { stub } 170 | end 171 | 172 | should 'return nil if the connection class matches PostgreSQLAdapter' do 173 | assert_nil self.postgres?(@model) 174 | end 175 | end 176 | 177 | context 'the connection class matches PostgreSQLAdapter' do 178 | setup do 179 | class PostgreSQLAdapter; end 180 | mock(@model).connection { PostgreSQLAdapter.new } 181 | end 182 | 183 | should 'return true' do 184 | assert self.postgres?(@model) 185 | end 186 | end 187 | end 188 | end 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /test/lib/rails-jquery-autocomplete/orm/mongo_mapper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module RailsJQueryAutocomplete 4 | module Orm 5 | class MongoMapperTest < Test::Unit::TestCase 6 | include RailsJQueryAutocomplete::Orm::MongoMapper 7 | 8 | context "#mongo_mapper_get_autocomplete_order" do 9 | context "order is specified" do 10 | should 'returns the parametrized order for Mongoid' do 11 | assert_equal [[:field, :asc], [:state, :desc]], 12 | mongo_mapper_get_autocomplete_order(:method, :order => 'field ASC, state DESC') 13 | end 14 | end 15 | 16 | context 'order is not specified' do 17 | should 'return the method ordered ASC by default' do 18 | assert_equal [[:method, :asc]], 19 | mongo_mapper_get_autocomplete_order(:method, {}) 20 | end 21 | end 22 | end 23 | 24 | context "#mongo_mapper_get_autocomplete_items" do 25 | setup do 26 | @model = mock(Object) 27 | 28 | @parameters = { 29 | :model => @model, 30 | :method => :field, 31 | :term => 'query', 32 | :options => {:full => false} 33 | } 34 | mock(self).get_autocomplete_limit(anything) { 10 } 35 | mock(self).mongo_mapper_get_autocomplete_order(anything, anything) { [[:order, :asc]] } 36 | end 37 | 38 | context 'not a full search' do 39 | should 'do stuff' do 40 | mock(@model).where({:field=>/^query.*/i}).mock!.limit(10). 41 | mock!.sort([[:order, :asc]]) 42 | 43 | mongo_mapper_get_autocomplete_items(@parameters) 44 | end 45 | end 46 | 47 | context 'full search' do 48 | should 'return a full search query' do 49 | @parameters[:options] = {:full => true} 50 | 51 | mock(@model).where({:field => /.*query.*/i}).mock!.limit(10). 52 | mock!.sort([[:order, :asc]]) 53 | 54 | mongo_mapper_get_autocomplete_items(@parameters) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/lib/rails-jquery-autocomplete/orm/mongoid_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module RailsJQueryAutocomplete 4 | module Orm 5 | class MongoidTest < Test::Unit::TestCase 6 | include RailsJQueryAutocomplete::Orm::Mongoid 7 | 8 | context "#mongoid_get_autocomplete_order" do 9 | context "order is specified" do 10 | should 'returns the parametrized order for Mongoid' do 11 | assert_equal [[:field, :asc], [:state, :desc]], 12 | mongoid_get_autocomplete_order(:method, :order => 'field ASC, state DESC') 13 | end 14 | end 15 | 16 | context 'order is not specified' do 17 | should 'return the method ordered ASC by default' do 18 | assert_equal [[:method, :asc]], 19 | mongoid_get_autocomplete_order(:method, {}) 20 | end 21 | end 22 | end 23 | 24 | context "#mongoid_get_autocomplete_items" do 25 | setup do 26 | @model = mock(Object) 27 | 28 | @parameters = { 29 | :model => @model, 30 | :method => :field, 31 | :term => 'query', 32 | :options => {:full => false} 33 | } 34 | mock(self).get_autocomplete_limit(anything) { 10 } 35 | mock(self).mongoid_get_autocomplete_order(anything, anything) { [[:order, :asc]] } 36 | end 37 | 38 | context 'not a full search' do 39 | should 'do stuff' do 40 | mock(@model).where({:field=>/^query/i}).mock!.limit(10). 41 | mock!.order_by([[:order, :asc]]) 42 | 43 | mongoid_get_autocomplete_items(@parameters) 44 | end 45 | end 46 | 47 | context 'full search' do 48 | should 'return a full search query' do 49 | @parameters[:options] = {:full => true} 50 | 51 | mock(@model).where({:field=>/.*query.*/i}).mock!.limit(10). 52 | mock!.order_by([[:order, :asc]]) 53 | 54 | mongoid_get_autocomplete_items(@parameters) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/lib/rails-jquery-autocomplete/simple_form_plugin_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'view_test_helper' 3 | 4 | module RailsJQueryAutocomplete 5 | class SimpleFormPluginTest < ActionView::TestCase 6 | 7 | should "apply a class of 'autocomplete'" do 8 | with_input_for @user, :name, :autocomplete 9 | assert_select "input#user_name.autocomplete[type=text][name='user[name]']" 10 | end 11 | 12 | should "add a data-autocomplete attribute with the provided :url" do 13 | with_input_for @user, :name, :autocomplete, :url => '/test' 14 | assert_select "input#user_name[data-autocomplete=/test]" 15 | end 16 | 17 | should "add a data-update-elements attribute with encoded data if passed an :update_elements option" do 18 | with_input_for @user, :name, :autocomplete, :update_elements => { :id => '#this', :ego => '#that' } 19 | assert_select "input#user_name[data-update-elements='{"id":"#this","ego":"#that"}']" 20 | end 21 | 22 | should "not add a data-update-elements attribute if not passed an :update_elements option" do 23 | with_input_for @user, :name, :autocomplete, :url => '/test' 24 | assert_no_select "input#user_name[data-update-elements]" 25 | end 26 | 27 | should "add arbitrary html options, if specified" do 28 | with_input_for @user, :name, :autocomplete, :input_html => { :class => "superego" } 29 | assert_select "input#user_name.superego" 30 | end 31 | 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/lib/rails-jquery-autocomplete_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module RailsJQueryAutocomplete 4 | class RailsJQueryAutocompleteTest < ActionController::TestCase 5 | ActorsController = Class.new(ActionController::Base) 6 | ActorsController.autocomplete(:movie, :name) 7 | 8 | class ::Movie ; end 9 | 10 | context '#autocomplete_object_method' do 11 | setup do 12 | @controller = ActorsController.new 13 | @items = {} 14 | @options = { :display_value => :name } 15 | end 16 | 17 | should 'respond to the action' do 18 | assert_respond_to @controller, :autocomplete_movie_name 19 | end 20 | 21 | should 'render the JSON items' do 22 | mock(@controller).get_autocomplete_items({ 23 | :model => Movie, :method => :name, :options => @options, :term => "query" 24 | }) { @items } 25 | 26 | mock(@controller).json_for_autocomplete(@items, :name, nil) 27 | get :autocomplete_movie_name, :term => 'query' 28 | end 29 | 30 | context 'no term is specified' do 31 | should "render an empty hash" do 32 | mock(@controller).json_for_autocomplete({}, :name, nil) 33 | get :autocomplete_movie_name 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | 4 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 5 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 6 | 7 | ENV["RAILS_ENV"] = "test" 8 | module Rails 9 | def self.env 10 | ActiveSupport::StringInquirer.new("test") 11 | end 12 | end 13 | 14 | require 'minitest/autorun' 15 | require 'rails/all' 16 | require 'mongoid' 17 | require 'mongo_mapper' 18 | require 'shoulda' 19 | require 'test/unit/rr' 20 | require 'rails/test_help' 21 | require 'rails-jquery-autocomplete' 22 | 23 | module RailsJQueryAutocomplete 24 | class Application < ::Rails::Application 25 | end 26 | end 27 | 28 | RailsJQueryAutocomplete::Application.routes.draw do 29 | match '/:controller(/:action(/:id))' 30 | end 31 | 32 | ActionController::Base.send :include, RailsJQueryAutocomplete::Application.routes.url_helpers 33 | 34 | class Test::Unit::TestCase 35 | 36 | end 37 | 38 | -------------------------------------------------------------------------------- /test/view_test_helper.rb: -------------------------------------------------------------------------------- 1 | class User 2 | extend ActiveModel::Naming 3 | include ActiveModel::Conversion 4 | 5 | attr_accessor :id, :name, :description, :created_at, :updated_at 6 | 7 | def initialize(options={}) 8 | @new_record = false 9 | options.each do |key, value| 10 | send("#{key}=", value) 11 | end if options 12 | end 13 | 14 | def new_record! 15 | @new_record = true 16 | end 17 | 18 | def persisted? 19 | !@new_record 20 | end 21 | end 22 | 23 | class MockController 24 | attr_writer :action_name 25 | 26 | def _routes 27 | self 28 | end 29 | 30 | def action_name 31 | defined?(@action_name) ? @action_name : "edit" 32 | end 33 | 34 | def url_for(*args) 35 | "http://example.com" 36 | end 37 | 38 | def url_helpers 39 | self 40 | end 41 | 42 | def hash_for_user_path(*args); end 43 | def hash_for_users_path(*args); end 44 | end 45 | 46 | class MockResponse 47 | 48 | def initialize(test_case) 49 | @test_case = test_case 50 | end 51 | 52 | def content_type 53 | 'text/html' 54 | end 55 | 56 | def body 57 | @test_case.send :output_buffer 58 | end 59 | end 60 | 61 | class ActionView::TestCase 62 | include RailsJQueryAutocomplete::Autocomplete 63 | include SimpleForm::ActionViewExtensions::FormHelper 64 | 65 | setup :set_controller 66 | setup :set_response 67 | setup :setup_new_user 68 | 69 | def assert_no_select(selector, value = nil) 70 | assert_select(selector, :text => value, :count => 0) 71 | end 72 | 73 | def with_concat_form_for(*args, &block) 74 | concat simple_form_for(*args, &block) 75 | end 76 | 77 | def with_input_for(object, attribute_name, type, options={}) 78 | with_concat_form_for(object) do |f| 79 | f.input(attribute_name, options.merge(:as => type)) 80 | end 81 | end 82 | 83 | def set_controller 84 | @controller = MockController.new 85 | end 86 | 87 | def set_response 88 | @response = MockResponse.new(self) 89 | end 90 | 91 | def setup_new_user 92 | @user = User.new( 93 | :id => 1, 94 | :name => 'New in SimpleForm!', 95 | :description => 'Hello!', 96 | :created_at => Time.now 97 | ) 98 | end 99 | 100 | def protect_against_forgery? 101 | false 102 | end 103 | 104 | def user_path(*args) 105 | '/users' 106 | end 107 | alias :users_path :user_path 108 | end 109 | --------------------------------------------------------------------------------